SFINAE template specialization precedence

五迷三道 提交于 2019-12-10 01:33:49

问题


#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, decltype(
  std::declval<const T&>().begin(),
  std::declval<const T&>().end(),
  void()
)> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

I was expecting the third template to be more specialized than the second, but I got an ambiguous template instantiation.

Is there a way to make the third template more specialized? Explicitly checking for whether T is an std::array in the second template won't work for me. I'm writing a library and would like users to be able to define their own specializations of the trait. The second template is intended to be a generic specialization for containers in absence of a more specific trait.


回答1:


#include <iostream>
#include <array>
#include <vector>

template <typename T, typename SFINAE=void>
struct trait;

template <typename T>
struct trait<T, std::void_t<decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>> {
  static const char* name() { return "Container"; }
};

template <typename T, std::size_t N>
struct trait<std::array<T,N>,void> {
  static const char* name() { return "std::array"; }
};

int main(int argc, char* argv[]) {
  std::cout << trait<std::vector<int>>::name() << std::endl;
  std::cout << trait<std::array<int,2>>::name() << std::endl;
}

EDIT

First off, no guarantee for the following its really more a guess than a prove. Maybe someone else can correct, extend copy paste it or whatever.

However my first guess after seeing the question was to use std::void_t. I am pretty sure I have seen something like this before, but yeah would also not guarantee it. In order to show that std::void_t can be used we have to show that "one template specialization is more specific than the other". And we do this by checking partial order. I'll mimic the above with the following which is a bit more short.

template <typename T, typename SFINAE=void>
struct trait;
//#1
template <typename T>struct trait<T, std::void_t<decltype(std::declval<T>().begin())>>
{
  static const char* name() { return "Container"; }
};
//#2
template <typename T>struct trait<std::vector<T>,void> {
  static const char* name() { return "std::vector"; }
};

I am not going to explain how partial ordering is done, would take too long. After transforming to functions etc... you end up with something similar to the following.

//#2 from #1: f(trait<std::vector<T>,void>) from f(trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>)
    //P=trait<std::vector<T>,void>
    //A=trait<__ANY_TYPE__, std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>>
        //P1=std::vector<T>
        //A1=__ANY_TYPE__
        //P2=void
        //A2=std::void_t<decltype(std::declval<__ANY_TYPE__>().begin())>
        //==> T=? --> fail, #2 from #1 is not working

Now we have to show that #1 from #2 is working. If so we have shown that #2 is more specialized.

//#1 from #2: f(trait<T, std::void_t<decltype(std::declval<T>().begin())>>) from f(trait<std::vector<__ANY_TYPE__>,void>)
    //P=trait<T, std::void_t<decltype(std::declval<T>().begin())>>
    //A=trait<std::vector<__ANY_TYPE__>,void>
        //P1=T
        //A1=std::vector<__ANY_TYPE__>
        //P2=std::void_t<decltype(std::declval<T>().begin())> //(*)
        //A2=void
        //==> T=std::vector<__ANY_TYPE__> ok #1 from #2 works

Thats basically my sketch without checking the standard or anything else. I am pretty sure you can find it somewhere in the endless lines of the standard...

If you paid attention, you'll have noticed the (*). This line is basically the only important one if you want to use decltype(...). My guess is that using decltype(...) is leading to non-deduced context for the right hand side which is maybe not allowing to use the T from the P1/A1 deduction. But yeah this is basically the reason why i did not included an answer first to the working std::void_t solution. Finally the alternative std::void_t definition with typename ... is I think non-deduced context too just like decltype(...), due to the typename part.


EDIT

Just to add a few final lines. In principle there should not be a problem with decltype sfinae. Ok its non-deduced context, but why is it a problem? The only thing I can think of, is that non-deduced context has some special rules in combination with partial ordering...



来源:https://stackoverflow.com/questions/52462410/sfinae-template-specialization-precedence

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!