Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specialize a template with template-tempate parameters

Edit at the end

I have a function which takes a template:

template <template <typename ...> class P, typename ... Args>
void f(const P<Args...> &p)
{
    std::cout << "Template with " << sizeof...(Args) << " parameters!\n";
}

It works pretty good with any kind of templates I've tested so far:

f(std::valarray<int>{});    // Prints: "Template with 1 parameters!"
f(std::pair<char, char>{}); // Prints: "Template with 2 parameters!"
f(std::set<float>{});       // Prints: "Template with 3 parameters!"
f(std::map<int, int>{});    // Prints: "Template with 4 parameters!"

But, let's suppose I want to specialize the template for when it takes a template with two parameters, the code below doesn't work:

template <>
void f<template <typename, typename> class P, typename A, typename B>(const P<A, B> &p)
{
    std::cout << "Special case!\n";
}

parse error in template argument list
 void f<template <typename, typename> class P, typename A, typename B>(const P<Args...> &p) { std::cout << "Template with " << sizeof...(Args) << " parameters!\n"; }
      ^
'P' does not name a type
 void f<template <typename, typename> class P, typename A, typename B>(const P<Args...> &p) { std::cout << "Template with " << sizeof...(Args) << " parameters!\n"; }
                                                                             ^
expected ',' or '...' before '<' token
 void f<template <typename, typename> class P, typename A, typename B>(const P<Args...> &p) { std::cout << "Template with " << sizeof...(Args) << " parameters!\n"; }
                                                                              ^
template-id 'f<<expression error> >' for 'void f(int)' does not match any template declaration
 void f<template <typename, typename> class P, typename A, typename B>(const P<Args...> &p) { std::cout << "Template with " << sizeof...(Args) << " parameters!\n"; }

AFAIK it's pretty simple with the other types of template parameters:

// Type parameter
template <typename TYPE>
void f(TYPE) { std::cout << "Type!\n"; }

// Non-type parameter
template <int VALUE>
void f() { std::cout << "Value " << VALUE << "!\n"; }

// Type parameter specialization
template <>
void f(float) { std::cout << "Special type case!\n"; }

// Non-type parameter specialization
template <>
void f<666>() { static_assert(false, "Forbidden number!"); }

How can I achieve this functionality with template-template templates?

Edit:

As pointed by orlp and angew function templates cannot be partially specialised, so I should stick to object templates, here is my attempt:

template <template <typename ...> class P, typename ... Args>
struct c
{
    using type = P<Args...>; 
    const std::size_t count = sizeof...(Args);

    void f(const type &t)
    {
        std::cout << "Template with " << sizeof...(Args) << " parameters!\n";
    }
};

template <template <typename, typename> class P, typename A, typename B>
struct c<P, A, B> 
{
    using type = P<A, B>; 

    void f(const type &t)
    {
        std::cout << "Spezialized 2 parameters!\n";
    }
};

It works:

c<std::valarray, int>                    c_valarray_int;
c<std::pair, int, char>                  c_pair_int_char;
c<std::vector, int, std::allocator<int>> c_vector_int;
c<std::map, int, int>                    c_map_int_int;

c_valarray_int.f({});  // Prints: "Template with 1 parameters!"
c_pair_int_char.f({}); // Prints: "Spezialized with 2 parameters!"
c_vector_int.f({});    // Prints: "Spezialized with 2 parameters!"
c_map_int_int.f({});   // Prints: "Template with 2 parameters!" (expected)

But now, I should specify all the parameters instead of let the compiler guess the whole thing, well... that's not a tragedy.

like image 291
PaperBirdMaster Avatar asked Mar 13 '15 09:03

PaperBirdMaster


2 Answers

You're attempting partial specialisation of your function template—specialisation for any template with two template parameters. That is not allowed in C++. Function templates cannot be partially specialised.

Look at it another way: the explicit specialisations you've shown for tempalte type parameters and template non-type parameters use concrete arguments. To explicitly specialise your function template, you'd do something like this:

template <>
void f<std::pair, int, double>(const std::pair<int, double> &p)
{
    std::cout << "Special case!\n";
}

In your original code, P, A and B are still "unbound"—they're parameters, not arguments. An explicit specialisation cannot have template parameters; all template parameters of the primary template being specialised must be bound to concrete arguments provided by that specialisation.


To address your edit: you can still keep the function template interface with argument deduction, and simply forward the call to a (potentially partially specialised) class template, like this:

template <template <typename ...> class P, typename ... Args>
struct c
{
    using type = P<Args...>; 
    static const std::size_t count = sizeof...(Args);

    static void f(const type &t)
    {
        std::cout << "Template with " << sizeof...(Args) << " parameters!\n";
    }
};

template <template <typename, typename> class P, typename A, typename B>
struct c<P, A, B> 
{
    using type = P<A, B>; 

    static void f(const type &t)
    {
        std::cout << "Spezialized 2 parameters!\n";
    }
};

template <template <class ...> class P, class... Args>
void f(const P<Args...> &t)
{
    c<P, Args...>::f(t);
}

[Live example]

like image 128
Angew is no longer proud of SO Avatar answered Sep 19 '22 08:09

Angew is no longer proud of SO


Well, the proper syntax for doing what you're trying to do is this:

template <template<typename, typename> class P, typename A, typename B>
void f<P, A, B>(const P<A, B> &p) {
    std::cout << "Special case!\n";
}

However, this is partial specialization, which is not allowed on functions. You can turn your function into a class with a static method though, and use a wrapper function calling the static method of the class.

like image 28
orlp Avatar answered Sep 22 '22 08:09

orlp