Multiple typename arguments in c++ template?

Jichao picture Jichao · Nov 12, 2013 · Viewed 54.6k times · Source

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?

Answer

Dietmar K&#252;hl picture Dietmar Kühl · Nov 12, 2013

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).