Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this dependent type not count as specialization using the template argument?

I'm trying to group specializations together to avoid writing them multiple times. For example, in the below code, I try to specialize "float" and "double" as one case of implementation for foo::func(); I then use another implementation for "bool."

template<typename T> struct foo;

template<typename T> struct bar;
template<> struct bar<float> { typedef float Type; };
template<> struct bar<double> { typedef double Type; };

/* specialize for float and double here */
template<typename T> struct foo<typename bar<T>::Type> {
    static void func() { ... }
};

template<> struct foo<bool> {
    static void func() { ... }
};

This errors out in GCC 4.4.3. (This is a target compiler, because it's stock for Ubuntu Server 10.04 LTS, which allegedly has three more years to live.) The error is:

foo.cpp:8: error: template parameters not used in partial specialization:
foo.cpp:8: error:         ‘T’

The error refers to the first specialization of foo (for "float" and "double.")

I don't see what part of C++ I'm violating here -- if anyone knows the chapter and verse, I'd appreciate it. Also, if someone knows of another way of accomplishing the same goal (re-using specializations for certain groups of types, without unnecessarily verbose code,) I'd also appreciate any suggestions!

like image 475
Jon Watte Avatar asked Apr 17 '12 03:04

Jon Watte


People also ask

What is meant by template specialization in C++?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.


2 Answers

template<typename T> struct foo<typename bar<T>::Type> {
    static void func() { ... }
};

You're using T in non-deducible context, hence the compiler cannot deduce T even if it knows the value of bar<T>::Type.

Suppose you write,

foo<double> foodouble;

then you probably think, bar which is specialized with double will be selected when instantiating foo? That seems reasonable only if the compiler can make sure that there doesn't exist another specialization of bar which defines double as nested type, something like this:

template<> struct bar<int> { typedef double Type; };

Now bar<double>::Type and bar<int>::Type both give double. So the bottomline is: there could exist infinite number of specialization of bar , all of which may provide double as nested type, making it impossible for the compiler to uniquely deduce the template argument for bar class template.


You can use SFINAE as:

#include <iostream>

template<typename T> struct bar { typedef void type; };
template<> struct bar<float> { typedef bar<float> type; };
template<> struct bar<double> { typedef bar<double> type; };

template<typename T> 
struct foo : bar<T>::type
{
    static void func() 
    { 
        std::cout << "primary template for float and double" << std::endl; 
    }
};

template<> 
struct foo<bool> 
{
    static void func() 
    { 
       std::cout << "specialization for for bool" << std::endl; 
    }
};

int main()
{
     foo<float>::func();
     foo<double>::func();
     foo<bool>::func();
}

Output (online demo):

primary template for float and double
primary template for float and double
specialization for for bool

Note that struct foo : bar<T>::type is not a specialization anymore. It is a primary template. Also note that it may not be what you want, as it disables all instantiations of the class template with type argument other than float, double and bool; for example, you cannot use foo<int>. But then I also note that you've left the primary template undefined, so I hope this solution fits in with your requirement.

like image 199
Nawaz Avatar answered Sep 19 '22 04:09

Nawaz


I don't think that what you are attempting is possible. I found a similar question here. I don't have a link, but the relevant section of the c++ standard is 14.8.2.5. Here is a quote from the section on the deduction of template arguments:

The non-deduced contexts are:
— The nested-name-specifier of a type that was specified using a qualified-id.
— A non-type template argument or an array bound in which a subexpression references a template
parameter.
— A template parameter used in the parameter type of a function parameter that has a default argument
that is being used in the call for which argument deduction is being done.
— A function parameter for which argument deduction cannot be done because the associated function
argument is a function, or a set of overloaded functions (13.4), and one or more of the following apply:
— more than one function matches the function parameter type (resulting in an ambiguous deduc-
tion), or
— no function matches the function parameter type, or
— the set of functions supplied as an argument contains one or more function templates.
— A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter
does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list
type. [ Example:
template<class T> void g(T);
g({1,2,3});
// error: no argument deduced for T
— end example ]
— A function parameter pack that does not occur at the end of the parameter-declaration-clause.

In your case you are specifying the type using a qualified id, so the argument cannot be deduced.

Without giving it too much thought, as a quick workaround you could add a second non-type bool parameter to foo -- something like:

template<typename T, bool is_decimal = false>
struct foo {...}; // general case

template<typename T>
struct foo<T, true> { ... }; // case where T is float or double

Best of luck...

like image 40
ds1848 Avatar answered Sep 19 '22 04:09

ds1848