7

I know it's trivial to check if a type is an instantiation of a class-template taking TYPE parameters, as explained here: How can I check if a type is an instantiation of a given class template?

But... is it possible to have a variadic "is_instantiation_of__ntp<...>" (NTP standing for Non-Type Params) that would accept templates with any number of heterogeneous NON-TYPE parameters? For example:

template<char*, bool, long, size_t, char>
struct many_hetero_nontype_params_example {};

char HELLO_WORLD[] = "hello world";
using ManyHeteroNontypeParamsEx = many_hetero_nontype_params_example<HELLO_WORLD, false, -16, 777, 'x'>;

And be able to use it as follows:

is_instantiation_of__ntp<char*, bool, long, size_t, char, many_hetero_nontype_params_example, ManyHeteroNontypeParamsEx>::value

I know this can be easily implemented for NON-TYPE parameter lists that are 1) "homogeneous" (values of same type), or 2) few parameters (such that a non-variadic solution is practical). I even wrote a test case demonstrating these special cases (compiled with gcc 4.7.0) to give a better idea of what I'm talking about:

namespace test__is_instantiation_of__
{
    // is_instantiation_of
    template< template<typename...> class Template, typename T >
    struct is_instantiation_of : std::false_type {};
    template< template<typename...> class Template, typename... Args >
    struct is_instantiation_of< Template, Template<Args...> > : std::true_type {};

    // is_instantiation_of__homogeneous_nontype_params__
    template< typename NTP, template<NTP...> class Template, typename T >
    struct is_instantiation_of__homogeneous_nontype_params__ : std::false_type {};
    template< typename NTP, template<NTP...> class Template, NTP... Args >
    struct is_instantiation_of__homogeneous_nontype_params__< NTP, Template, Template<Args...> > : std::true_type {};

    // is_instantiation_of__fixedcount_nontype_params__
    template< typename NTP1, typename NTP2, template<NTP1, NTP2> class Template, typename T >
    struct is_instantiation_of__fixedcount_nontype_params__ : std::false_type {};
    template< typename NTP1, typename NTP2, template<NTP1, NTP2> class Template, NTP1 v1, NTP2 v2 >
    struct is_instantiation_of__fixedcount_nontype_params__< NTP1, NTP2, Template, Template<v1, v2> > : std::true_type {};

    // type_params_example
    template<typename T1, typename T2, typename T3>
    struct type_params_example {};

    // homogeneous_nontype_params_example
    template<bool B1, bool B2, bool B3, bool B4>
    struct homogeneous_nontype_params_example {};

    // fixedcount_nontype_params_example
    template<long L, char C>
    struct fixedcount_nontype_params_example {};

    using /*.........*/ TypeParamsEx = /*..........*/ type_params_example<std::string, std::tuple<long, void*>, double>;
    using  HomogenousNontypeParamsEx = homogeneous_nontype_params_example<true, false, true, false>;
    using  FixedCountNontypeParamsEx = fixedcount_nontype_params_example<777, 'x'>;

    void run()
    {
        using std::cout;
        using std::endl;

        if ( is_instantiation_of<type_params_example, TypeParamsEx>::value ) {
            cout << "[TypeParamsEx] specializes [type_params_example]" << endl;
        }
        if ( is_instantiation_of<type_params_example, HomogenousNontypeParamsEx>::value ) {
            cout << "[HomogenousNontypeParamsEx] specializes [type_params_example]" << endl;
        }
        if ( is_instantiation_of<type_params_example, FixedCountNontypeParamsEx>::value ) {
            cout << "[FixedCountNontypeParamsEx] specializes [type_params_example]" << endl;
        }

        if ( is_instantiation_of__homogeneous_nontype_params__<bool, homogeneous_nontype_params_example, TypeParamsEx>::value ) {
            cout << "[TypeParamsEx] specializes [homogeneous_nontype_params_example]" << endl;
        }
        if ( is_instantiation_of__homogeneous_nontype_params__<bool, homogeneous_nontype_params_example, HomogenousNontypeParamsEx>::value ) {
            cout << "[HomogenousNontypeParamsEx] specializes [homogeneous_nontype_params_example]" << endl;
        }
        if ( is_instantiation_of__homogeneous_nontype_params__<bool, homogeneous_nontype_params_example, FixedCountNontypeParamsEx>::value ) {
            cout << "[FixedCountNontypeParamsEx] specializes [homogeneous_nontype_params_example]" << endl;
        }

        if ( is_instantiation_of__fixedcount_nontype_params__<long, char, fixedcount_nontype_params_example, TypeParamsEx>::value ) {
            cout << "[TypeParamsEx] specializes [fixedcount_nontype_params_example]" << endl;
        }
        if ( is_instantiation_of__fixedcount_nontype_params__<long, char, fixedcount_nontype_params_example, HomogenousNontypeParamsEx>::value ) {
            cout << "[HomogenousNontypeParamsEx] specializes [fixedcount_nontype_params_example]" << endl;
        }
        if ( is_instantiation_of__fixedcount_nontype_params__<long, char, fixedcount_nontype_params_example, FixedCountNontypeParamsEx>::value ) {
            cout << "[FixedCountNontypeParamsEx] specializes [fixedcount_nontype_params_example]" << endl;
        }
    }
}

As expected, the output you get is:

[TypeParamsEx] specializes [type_params_example]
[HomeogenousNonTypeParamsEx] specializes [homogeneous_nontype_params_example]
[FixedCountNonTypeParamsEx] specializes [fixedcount_nontype_params_example]

The problem is none of those templates work for the many_hetero_nontype_params_example (above). I.e: a single, variadic "is_instantiation_of__ntp" that would accept templates with any number of heterogeneous non-type parameters.

I think if primary-templates weren't required to have parameter-packs at the end of the template parameter list, then this would be easy to implement. Or if it were possible to use a wrapper-struct/nested-struct approach. Here are my (failed) attempts:

namespace test__is_instantiation_of__nontypes__
{
    template<char*, bool, long, size_t, char>
    struct many_hetero_nontype_params_example {};

    char HELLO_WORLD[] = "hello world";
    using ManyHeteroNontypeParamsEx = many_hetero_nontype_params_example<HELLO_WORLD, false, -16, 777, 'x'>;

    /*
     * is_instantiation_of__nontypes_v1__ (version 1)
     * if uncommented, syntax error as expected ...
     *   error: parameter pack 'NTPs' must be at the end of the template parameter list
     *   error: parameter pack argument 'NTPs ...' must be at the end of the template argument list
     */
    //template< typename... NTPs, template<NTPs...> class Template, typename T >
    //struct is_instantiation_of__nontypes_v1__ : std::true_type {};
    //template< typename... NTPs, template<NTPs...> class Template, NTPs... NonTypeArgs >
    //struct is_instantiation_of__nontypes_v1__< NTPs..., Template, Template<NonTypeArgs...> > : std::true_type {};

    /*
     * is_instantiation_of__nontypes_v2__ (version 2)
     * no syntax error (but see instantiation errors below)
     */
    template<typename... NTPs>
    struct is_instantiation_of__nontypes_v2__
    {
        template< template<NTPs...> class Template, typename T >
        struct impl : std::false_type {};

        template< template<NTPs...> class Template, NTPs... NonTypeArgs >
        struct impl< Template, Template<NonTypeArgs...> > : std::true_type {};
    };

    void run()
    {
        /*
         * uncommented since "v1" template has syntax error, but this is how it would be used ...
         */
        //if ( is_instantiation_of__nontypes_v1__<char*, bool, long, size_t, char, many_hetero_nontype_params_example, ManyHeteroNontypeParamsEx>::value ) {
        //  std::cout << "yes" << std::endl;
        //}

        /*
         * "v2" template has no syntax error, but the following attempt to use it results in these errors ...
         *
         *   error: type/value mismatch at argument 1 in template parameter list for 'template<class ... NTPs> template<template<template<NTPs ...<anonymous> > class Template, class T> template<class ... NTPs> template<NTPs ...<anonymous> > class Template, class T> struct is_instantiation_of__nontypes_v2__<NTPs>::impl'
         *   error: expected a template of type 'template<class ... NTPs> template<NTPs ...<anonymous> > class Template', got 'template<char* <anonymous>, bool <anonymous>, long int <anonymous>, long unsigned int <anonymous>, char <anonymous> > struct many_hetero_nontype_params_example'
         */
        //if ( is_instantiation_of__nontypes_v2__<char*, bool, long, size_t, char>::impl<many_hetero_nontype_params_example, ManyHeteroNontypeParamsEx>::value ) {
        //  std::cout << "yes" << std::endl;
        //}
    }
}

...

Is a variadic solution for this even possible?

Thanks in advance.

Community
  • 1
  • 1
etherice
  • 1,761
  • 15
  • 25
  • I think I misunderstood your question in my original answer, sorry. Will delete it and provide a better answer. – Andy Prowl Mar 02 '13 at 16:00
  • Your original answer explained why there is no obvious solution to this problem, such as declaring a parameter-pack "typename... ScalarTypes" to then declare the template-template "template class TheTemplate" in the same parameter-list. It doesn't cover other possible solutions, such as the is_instantiation_of__nontypes_v2__ attempt I posted, which uses an enclosing struct for declaring the parameter-pack for the template-template param declared in the parameter-list of the nested struct. – etherice Mar 02 '13 at 16:18
  • If you could have explained why that "v2" attempt wouldn't work (specifically, the gcc errors upon trying to use it), I would have been pretty convinced that this can't be done and would have accepted the answer. But, it would be nice to know for sure. Thanks for looking into this. – etherice Mar 02 '13 at 16:22
  • This should help clarify the question: If you can get either one of the uses of "is_instantiation_of__nontypes_vN__" in test__is_instantiation_of__nontypes__::run() to work (for many_hetero_nontype_params_example), it will answer the question. Or prove there is no possible solution. – etherice Mar 02 '13 at 16:28

1 Answers1

5

You have probably hit a compiler bug. I tried to reduce this to a simpler example:

#include <iostream>

template<bool, char> struct A { };

template<typename... Ts>
struct test
{  
    template<typename T>
    struct impl : std::false_type {};

    template<Ts... Args>
    struct impl<A<Args...>> : std::true_type {};
};

int main()
{
    using IA = A<false, 'x'>;
    std::cout << ((test<bool, char>::impl<IA>::value) ? "Y" : "N");
}

GCC 4.7.2 compiles this, but the compiled program prints the wrong output (N). On the other hand, Clang 3.2 gets this right, and the compiled program prints the correct output (Y).

Here is a slightly modified version of the above program, where the test class template much resembling your is_instantiation_of__nontypes_v2__ class template:

#include <iostream>

template<bool, char> struct A {};

template<typename... Ts>
struct test
{  
    template<template<Ts...> class TT, typename T>
    struct impl : std::false_type {};

    template<template<Ts...> class TT, Ts... Args>
    struct impl<TT, TT<Args...>> : std::true_type {};
};

int main()
{
    using IA = A<false, 'x'>;
    std::cout << ((test<bool, char>::impl<A, IA>::value) ? "Y" : "N");
}

While Clang compiles this and the compiled program prints the correct output (Y), GCC emits the following compilation error:

expected a template of type 'template<class ... Ts> template<Ts ...<anonymous> > class TT', got 'template<bool <anonymous>, char <anonymous> > struct A'.

It looks like GCC does not recognize that the first template template parameter should have a template argument list given by the expansion of Ts. Therefore, it seems to me this is a bug of GCC.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Correct regarding the lack of "literal_type..." argument-pack making this more difficult. But, it could still be possible if there were some way to declare "typename... ScalarTypes" in the template parameter list, and then define the template-template as "template class TheTemplate" ... The problem is that the parameter-pack must be at the end of the list (after TheTemplate is declared). You can see my "failed attempts" at the end of the post... I'm looking for a similar technique that actually works. "is_instantiation_of__nontypes_v2__" comes close I think. – etherice Mar 02 '13 at 15:12
  • @etherice: I tried to comment a bit on that in the last edit to my answer. – Andy Prowl Mar 02 '13 at 15:16
  • That rule (which you emboldened) is applicable to the "v1" attempt, but it does **not** seem applicable to the "v2" attempt. "typename... NTPs" is declared in the template-parameter-list of the enclosing struct, not the nested struct. As I stated, it DOES compile, but see the gcc errors when I try to use it. Can you explain the error it produces? – etherice Mar 02 '13 at 15:23
  • I had a suspicion that might be the case, given that the error from gcc made no sense and I was not aware of any rule that would make it illegal or ill-formed in terms of the language. Thanks for confirming that it works in other compilers. I'm marking this SO question as answered, but I'm curious if you did any research into whether this is a known bug in gcc and if it's been fixed in a newer version. – etherice Mar 02 '13 at 23:41
  • @etherice: I believe this hasn't been fixed (GCC 4.8.0 beta suffers from the same problem). Maybe you could file a bug report. – Andy Prowl Mar 02 '13 at 23:42
  • Good to know. I'll go ahead and submit a report. – etherice Mar 03 '13 at 00:20
  • Could you explain why the code is different between your two liveworkspace links? The GCC one has an extra `char const *` parameter that the clang one does not. – LB-- May 06 '13 at 14:19
  • @LB--: Probably a bad copy-paste. Anyway if you take the example from Clang's output and use it with GCC, it still gives the wrong result – Andy Prowl May 06 '13 at 14:31
  • Finally got around to filing the bug report (since I just tested against the top of the 4.8 branch and it still hasn't been fixed)... http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57239 – etherice May 10 '13 at 17:50