Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templated class specialization where template argument is a template

Tags:

c++

templates

I wondering if something similar to this is possible. Basically, I have a templated class that occasionally takes objects of templated classes. I would like to specialize it (or just a member function)for a specific templated class, but the 'generic' form of that class.

template<typename T, typename S>
class SomeRandomClass
{
    //put something here
};

template<typename T>
class MyTemplateClass
{
    void DoSomething(T & t) {
       //...something
    }
};

template<>
void MyTemplateClass< SomeRandomClass<???> >::DoSomething(SomeRandomClass<???> & t)
{
    //something specialized happens here
}

Replacing the question marks with appropriate types (double, etc) works, but I would like it to remain generic. I don't know what to put there, as any types wouldn't have been defined. I've looked around, and learned about template template parameters, and tried various combinations to no avail. Thanks for the help!

like image 597
Benjamin Pritchard Avatar asked Nov 15 '10 23:11

Benjamin Pritchard


People also ask

Can template parameter be a template?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

When we specialize a function template it is called?

To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double.

What is a template argument in C++?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

What is explicit template specialization?

Explicit (full) specializationAllows customizing the template code for a given set of template arguments.


3 Answers

It's possible to specialize the class like this

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t) { /* something */ }
};

It's not possible to specialize just the member method, because the specialization is on the class as a whole, and you have to define a new class. You can, however, do

template <>
template <typename T,typename S>
class MyTemplateClass <SomeRandomClass<T,S> >
{
    void DoSomething(SomeRandomClass<T,S>& t);
};

template <>
template <typename T,typename S>
void MyTemplateClass<SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S>& t)
{
    // something
}

to split up the declaration and definition.

like image 196
Ryan Calhoun Avatar answered Oct 17 '22 03:10

Ryan Calhoun


I'm not completely sure why @Ryan Calhoun specialized the way he did but here's a more terse example:

// class we want to specialize with later on
template<typename T, typename S>
struct SomeRandomClass
{
    int myInt = 0;
};

// non-specialized class
template<typename T>
struct MyTemplateClass
{
    void DoSomething(T & t) 
    {
       std::cout << "Not specialized" << std::endl;
    }
};

// specialized class
template<typename T, typename S>
struct MyTemplateClass< SomeRandomClass<T, S> >
{
    void DoSomething(SomeRandomClass<T,S> & t) 
    {
       std::cout << "Specialized" << std::endl;
    }
};

You can see that you don't need the redundant syntax used in the accepted answer:

template<>
template<typename T, typename S>

Working Demo


Alternative

You can use type_traits and tag-dispatch within your non-specialized class to specialize just the function.

Let's first make a concept for is_random_class:

// concept to test for whether some type is SomeRandomClass<T,S>
template<typename T>
struct is_random_class : std::false_type{};

template<typename T, typename S>
struct is_random_class<SomeRandomClass<T,S>> : std::true_type{};

And then let's declare our MyTemplateClass again, but this time not templated (because we're not specializing) so we'll call it MyNonTemplatedClass:

class MyNonTemplatedClass
{
    
    public:
    template<typename T>
    void DoSomething(T & t) 
    {
       DoSomethingHelper(t, typename is_random_class<T>::type());
    }
    // ...

Notice how DoSomething is now templated, and it's actually calling a helper instead of implementing the logic itself?

Let's break down the line:

DoSomethingHelper(t, typename is_random_class<T>::type());
  • t is as-before; we're passing along the argument of type T&
  • typename is_random_class<T>::type()
    • is_random_class<T> is our concept, and since it derives from std::true_type or std::false_type it will have a ::type defined within the class (Google for "type traits")
    • ::type() 'instantiates' the type specified by is_random_class<T>::type. I say it in quotation marks because we're really going to throw that away as we see later
    • typename is required because the compiler doesn't know that is_random_clas<T>::type actually names a type.

Now we're ready to look at the rest of MyNonTemplatedClass:

    private:
    //use tag dispatch. If the compiler is smart it won't actually try to instantiate the second param
    template<typename T>
    void DoSomethingHelper(T&t, std::true_type)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }
    
    template<typename T>
    void DoSomethingHelper(T&t, std::false_type)
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

Full Working Demo v2 Here

Notice that our helper functions are named the same, but overloaded on the second parameter's type. We don't give a name to the parameter because we don't need it, and hopefully the compiler will optimize it away while still calling the proper function.

Our concept forces DoSomethingHelper(T&t, std::true_type) only if T is of type SomeRandomClass, and calls the other for any other type.

The benefit of tag dispatch

The main benefit of tag dispatch here is that you don't need to specialize your entire class if you only mean to specialize a single function within that class.

The tag dispatching will happen at compile time, which you wouldn't get if you tried to perform branching on the concept solely within the DoSomething function.


C++17

In C++17, this problem becomes embarrassingly easy using variable templates (C++14) and if constexpr (C++17).

We use our type_trait to create a variable template that will give us a bool value of true if the provided type T is of type SomeRandomClass, and false otherwise:

template<class T>
constexpr bool is_random_class_v = is_random_class<T>::value;

Then, we use it in a if constexpr expression that only compiles the appropriate branch (and discards the other at compile-time, so the check is at compile-time, not run-time):

struct MyNonTemplatedClass
{
    template<class T>
    void DoSomething(T& t) 
    {
        if constexpr(is_random_class_v<T>)
            std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
        else
            std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

type-traits were a way to simulate this without needing a class specialization.

Note that is_random_class here is a stand-in for an arbitrary constraint. In general, if you're only checking for a single nontemplated type, prefer a normal overload because it's more efficient on the compiler.

Demo


C++20

In C++20, we can take this a step further and use a constraint instead of if constexpr by using a requires clause on our templated member function. The downside is that we again move back to two functions; one that matches the constraint, and another that doesn't:

struct MyNonTemplatedClass
{
    template<class T>  requires is_random_class_v<T>
    void DoSomething(T& t)
    {
        std::cout << "Called DoSomething with SomeRandomClass whose myInt member has value " << t.myInt << std::endl;
    }
    
    template<class T> requires !is_random_class_v<T>
    void DoSomething(T&) 
    {
        std::cout << "Called DoSomething with a type that is not SomeRandomClass\n";
    }
};

Demo


Also in C++ 20, we could explicitly encode a concept and use abbreviated template syntax:

template<class T>
concept IsRandomClass = is_random_class_v<T>;

template<class T>
concept IsNotRandomClass = !is_random_class_v<T>;

// ...

template<IsRandomClass T>
void DoSomething(T& t)
{ /*...*/}

template<IsNotRandomClass T>
void DoSomething(T&)
{ /*...*/}

Demo

like image 30
AndyG Avatar answered Oct 17 '22 03:10

AndyG


All you need to do is just template on what you want to keep generic. Taking what you started with:

template<typename T, typename S>
void MyTemplateClass< SomeRandomClass<T,S> >::DoSomething(SomeRandomClass<T,S> & t)
{
    //something specialized happens here
}

EDIT:

Alternatively, if you only want to keep part of the SomeRandomClass generic, you could:

template<typename T>
void MyTemplateClass< SomeRandomClass<T,int> >::DoSomething(SomeRandomClass<T,int> & t)
{
    //something specialized happens here
}
like image 10
Jason Iverson Avatar answered Oct 17 '22 03:10

Jason Iverson