Question

How can I have multiple typename arguments in a c++ template?

#ifndef _CALL_TEMP_H
#define _CALL_TEMP_H

#include <string>
#include <iostream>

template <typename Sig>
class Foo;

template <typename A, typename B>
class Foo
{
    public:
        void output() {
            std::cout << a_ << b_ << std::endl;
        }
        A a_;
        B b_;
};

template <typename A, typename B, typename C>
class Foo
{
    public:
        void output() {
            std::cout << a_ << b_ << c_ << std::endl;
        }
        A a_;
        B b_;
        C c_;
};

#endif

Usage:

int main()
{
    Foo<int ,int> doubleint;
    doubleint.a_ = 1;
    doubleint.b_ = 2;
    doubleint.output();
//  Foo<int , int , std::string> comp;
//  comp.a_ = 1;
//  comp.b_ = 2;
//  comp.c_ = "haha";
//  comp.output();
    return 0;
}

But it will not compile. How could I make it compile?

Was it helpful?

Solution

Just declare a primary template with a variadic template and then specialize for each supported number of template arguments. For example:

#ifndef CALL_TEMP_H
#define CALL_TEMP_H

#include <iostream>

template <typename...> class Foo;

template <typename A, typename B>
class Foo<A, B>
{
public:
    void output() {
        std::cout << a_ << b_ << '\n';
    }
    A a_;
    B b_;
};

template <typename A, typename B, typename C>
class Foo<A, B, C>
{
public:
    void output() {
        std::cout << a_ << b_ << c_ << '\n';
    }
    A a_;
    B b_;
    C c_;
};

#endif

I you can't use C++11 and you want to retain a similar notation you'll need to simulate a variadic argument list with template default arguments. This will implicitly limit the number of templare arguments but since you are specializing the templates anyway, this limitation doesn't realky matter.

If it is acceptable to use a different notation you can also use something which looks like a function declaration to instantiate and specialize your template:

template <typename> class Foo;

template <typename A, typename B>
class Foo<void(A, B)> {
    ...
};
template <typename A, typename B, typename C>
class Foo<void(A, B, C)> {
    ...
};
...
Foo<void(int, int)>                   f2;
Foo<void(int, int, std::string)> f3;

Whether the change in notation is acceptable depends on your use of the class template. You won't achieve an ideal solution as with variadic templates without C++11, though.

BTW, don't overuse std::endl: use '\n' to mean end of line. If you really mean to flush the stream, use std::flush. Also _CALL_TEMP_H is a name reserved to the standard C++ library as are all names starting with an underscore followed by a capital character: do not use these names in your own code unless there is explicit permission to use them (e.g. __FILE__ and __LINE__ are reserved but explicit permission to use them is granted).

OTHER TIPS

If you have multiple versions of a template, you have to specialize a single version. If you want a different number of arguments, then the trick is to use a tag class to say "this argument is no argument", and have that as a default argument.

In your case, something like the following works (compiled and tested):

#include <iostream>

// tag class indicating "no member in this place"
struct nothing {};

template <typename A, typename B, typename C = nothing> // <- note default arg.
class Foo;

template <typename A, typename B>
class Foo<A, B, nothing> // <- note specialization
{
    public :
        void output() {
            std::cout << a_ << b_ << std::endl;
        }

        A a_;
        B b_;
};

template <typename A, typename B, typename C>
class Foo
{
    public :
        void output() {
            std::cout << a_ << b_ << c_ << std::endl;
        }

        A a_;
        B b_;
        C c_;
};

int main()
{
    Foo<int, int> doubleint;
    doubleint.a_ = 1;
    doubleint.b_ = 2;
    doubleint.output();

    Foo<int, int, int> tripleint;
    tripleint.a_ = 1;
    tripleint.b_ = 2;
    tripleint.c_ = 3;
    tripleint.output();
}

Note that this is essentially a re-invention of boost::tuple<>/std::tuple<>, which you should definitely read up on.

I think you're confusing specialization with overloading the same class name. You cannot create a class with the same name with multiple template arguments.

It won't compile because you can't have the same class defined multiple times with a different number of template arguments.

If you know the maximum number of template parameters you want to support, you could use partial specialisation:

// main template
template <typename A, typename B = void, typename C = void>
struct Foo
{
    void output() { std::cout << a_ << b_ << c_ << std::endl; }
    A a_;
    B b_;
    C c_;
};

// Partial specialisation for two parameters
template <typename A, typename B>
struct Foo<A, B, void>
{
    void output() { std::cout << a_ << b_ << c_ << std::endl; }
    A a_;
    B B_;
};

// Partial specialisation for one parameter
template <typename A>
struct Foo<A, void, void>
{
    void output() { std::cout << a_ << std::endl; }
    A a_;
};

If you're using C++11, another option would be to use variadic templates.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top