Search code examples
c++templatesc++11variadic

Parameter pack with default template argument


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)?


Solution

  • 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.