#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...
template <typename T, std::size_t N> struct trait<std::array<T,N>, void> { };
compiles for me, with gcc 8.1.1. Note thevoid
. – Souvenirstd::begin
andstd::end
functions as they are more generic (for example work for c-style arrays) – Skier