14

I want to check whether or not a template can be specialized using a given set of arguments. Here is the version for templates accepting only 1 argument:

#include <iostream>

template<template<typename...> class C, typename T>
struct is_valid_specialization {
    typedef struct { char _; } yes;
    typedef struct { yes _[2]; } no;

    template<template<typename...> class D, typename U>
    static yes test(D<U>*);
    template<template<typename...> class D, typename U>
    static no test(...);

    constexpr static bool value = (sizeof(test<C, T>(0)) == sizeof(yes));
};

template<typename T>
struct Test1 { };

template<typename T1, typename T2>
struct Test2 { };

template<typename...>
struct TestV { };

int main() {
    std::cout << "Test1<T>: " << is_valid_specialization<Test1, int>::value << std::endl;
    std::cout << "Test2<T>: " << is_valid_specialization<Test2, int>::value << std::endl;
    std::cout << "TestV<T>: " << is_valid_specialization<TestV, int>::value << std::endl;
}

This does the job for templates accepting only a single argument, but obviously I want to be able to use this with multiple arguments as well, so I tried this:

template<template<typename...> class C, typename... T>
struct is_valid_specialization {
    typedef struct { char _; } yes;
    typedef struct { yes _[2]; } no;

    template<template<typename...> class D, typename... U>
    static yes test(D<U...>*);
    template<template<typename...> class D, typename... U>
    static no test(...);

    constexpr static bool value = (sizeof(test<C, T...>(0)) == sizeof(yes));
};

Now this is where things get weird, because now value is always false.

Is there something I'm missing? What is so utterly different between these two versions? Is there another way to achieve this?

EDIT:
I've filed a bug report for both Clang and GCC

Tom Knapen
  • 2,277
  • 16
  • 31
  • When I don't know what's wrong with things like these, I comment out the fallback function (`no test(...)`) and see what the error message is saying. In this case, the compiler keeps complaining that there are too many template arguments to whatever D is. Beats me... – jrok Jan 29 '14 at 17:40
  • 2
    By the way, these only test whether the arguments match the template parameter list, not whether a full instantiation of the template would actually be successful. The latter is impossible. – Sebastian Redl Jan 29 '14 at 17:43

2 Answers2

9

The following is easier and works:

template<template<typename...> class C, typename... T>
struct is_valid_specialization {
    typedef struct { char _; } yes;
    typedef struct { yes _[2]; } no;

    template<template<typename...> class D>
    static yes test(D<T...>*);
    template<template<typename...> class D>
    static no test(...);

    constexpr static bool value = (sizeof(test<C>(0)) == sizeof(yes));
};

Live example

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • I saw "the deduction is too complicated" before you edited :) I see no reason why it would be impossible. – jrok Jan 29 '14 at 17:38
  • @jrok That's why I deleted it. I don't know what exactly causes the problem. – Daniel Frey Jan 29 '14 at 17:39
  • @jrok Yes, I saw that too. So basically it's just a shortcoming of GCC and Clang (can't test VS) at the moment? – Tom Knapen Jan 29 '14 at 17:40
  • 1
    @TomKnapen I'm pretty sure there is a reason, I always doubt my code before doubting both GCC and Clang :) – Daniel Frey Jan 29 '14 at 17:41
  • Please file a bug with both compilers. With Clang, even if the compiler is technically right about something here, the error message is still nonsense and needs to be improved. – Sebastian Redl Jan 29 '14 at 17:51
  • @SebastianRedl I've never filed a bug before, I wouldn't even know how to go about it. Where should I go to file one? – Tom Knapen Jan 29 '14 at 17:53
  • @TomKnapen: Clang: http://llvm.org/bugs/enter_bug.cgi - choose the Clang component. GCC has detailed instructions here: http://gcc.gnu.org/bugs/ In both cases you have to create an account, I'm afraid. Then, just post the snippet of code you posted here and explain what you expect it to do, as well as the error messages the compiler gives you. – Sebastian Redl Jan 29 '14 at 17:56
  • @TomKnapen Also, if you file a bug report, drop all includes if possible and use `static_assert` instead. It helps to resolve the issue much faster and easier. – Daniel Frey Jan 29 '14 at 18:23
  • 3
    Both [original](http://rextester.com/live/MIIMG71382) and [modified](http://rextester.com/live/VMKOG37148) codes work fine compiled on Visual C++ (except for `constexpr` keyword, of course). And these codes can be simplified using `std::true_type`, `std::false_type` and `std::is_same`. – Constructor Jan 29 '14 at 18:23
  • does this actually check if the instantiation would be valid, or just that you are passing an allowed number of template arguments? – Joseph Garvin Sep 15 '15 at 18:56
  • 1
    @JosephGarvin It checks the number and kind of arguments, not whether or not it can be instantiated. – Daniel Frey Sep 15 '15 at 19:10
1

The number of template parameters accepted by the function test (U...) is still not known even if you explicitly specify the first T... parameters. Additional U... template parameters might be deduced from the function parameters. Since the function parameter (0) doesn't help guessing the size of U..., the first template function is not instantiated. It is important to note that the template parameter D might also take an arbitrary number of parameters. The compiler shouldn't make any assumption.

GCC and Clang are right.

Nicolas
  • 404
  • 6
  • 14