16
#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.

SU3
  • 5,064
  • 3
  • 35
  • 66

1 Answers1

8
#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...

  • 1
    Interestingly, this works if `void_t` is defined as `template using void_t = void;`, but not for `template struct make_void { typedef void type; }; template using void_t = typename make_void::type;` – SU3 Sep 23 '18 at 04:18
  • We better wait for someone else to answer this. I am just too unsure and don't want to spread nonsense. –  Sep 23 '18 at 04:58
  • @SU3, This does not work with template partial specialization of the form `make_void::type` and `decltype(std::declval()/*...*/)` because during the process of [partial ordering of template class](http://eel.is/c++draft/temp.class.order), this form of template argument will be [none deduced context](http://eel.is/c++draft/temp.deduct#type-5) *The non-deduced contexts are: (5.1) The nested-name-specifier of a type that was specified using a qualified-id. (5.2) The expression of a decltype-specifier.* – Oliv Sep 23 '18 at 17:53
  • @Oliv If you write that up as an answer, I'll accept it. – SU3 Sep 23 '18 at 17:55
  • @Oliv You are right. so this alternative form is like decltype(...) non-deduced context, in contrast to `template using void_t = void;` which is not non-deduced context. But why is non-deduced context a problem here. In that sense that i can show that `std::void_t` is working but i cant show why decltype(...)` is not working. –  Sep 23 '18 at 18:01
  • @Su3, I have doubts! – Oliv Sep 23 '18 at 18:01