6
votes

In this code, I'm trying to generalize Test from using Arg to using Args.... The problem is the default template argument. What I have below compiles, except when I uncomment the commented-out line in main():

#include <iostream>
#include <type_traits>

struct A {
    void foo(int) const {}
    void foo(int, bool, char) const {}
};

template <typename...> struct voider { using type = void; };

template <typename... Ts>
using void_t = typename voider<Ts...>::type;

template <typename T, typename Arg, typename = void_t<T>>
struct Test : std::false_type {};

template <typename T, typename Arg>
struct Test<T, Arg, void_t<decltype(std::declval<T&>().foo(std::declval<Arg>()))>> :
    std::true_type {};

// Trying to generalize Test with Args... instead of Arg
template <typename T, typename, typename... Args> struct Check;

template <typename T, typename... Args>
struct Check<T, void_t<T>, Args...> : std::false_type {};

template <typename T, typename... Args>
struct Check<T, void_t<decltype(std::declval<T&>().foo(std::declval<Args>()...))>, Args...>
    : std::true_type {};

template <typename T, typename... Args>
using CheckArgs = Check<T, void_t<T>, Args...>;

int main() {
    std::cout << std::boolalpha << Test<A, int>::value << '\n';  // true
//  std::cout << CheckArgs<A, int, bool, char>::value << '\n';  // ambiguous
}

The last line in main() is ambiguous. Firstly, why is it ambiguous, while the first line in main() is not? And secondly, how to fix the code so that the last line in main will compile (it is supposed to evaluate to true since int, bool, char are arguments of A::foo)?

1
Compiles in VS2015, both return false - JVene
I didn't expect that. But the first line should definitely evaluate to true (that's what GCC 5.1.0 gives). - prestokeys
I haven't really researched why yet, but the fact this compiles here (second line too) means the compilers are quite different. That's to be expected. Both return false at the moment. - JVene
Ok, first one down... in the code you posted, change std::false_type to std::true_type in the declaration of Test, then the first line DOES return true; - JVene
But we must use std::false_type there, because that default specialization is reached only if decltype(std::declval<T&>().foo(std::declval<Arg>()) cannot be evaluated, i.e. A::foo does that not have that particular Arg as argument (which means Test<A,Arg>::value must evaluate to false). I really do think VS2015 is bugged in its output here. But as for compiling/not compiling the second line, I don't know if it is GCC or VS2015 that is buggy. - prestokeys

1 Answers

7
votes

You want

template <typename T, typename, typename... Args> 
struct Check : std::false_type {};

template <typename T, typename... Args>
struct Check<T, void_t<decltype(std::declval<T&>().foo(std::declval<Args>()...))>, Args...>
    : std::true_type {};

You want the primary template to provide the default case - which is false, and the partial specialization to supply the true case. When you write two partial specializations, both are viable, and there's no ordering between the two, so it ends up being ambiguous

And this is just reimplementing a more constrained version of std::experimental::is_detected.