Quest
Quest

Reputation: 2843

Variadic template method specialization

This is the code I currently have:

class Foo
{
public:
    template<typename T, typename... Args>
    void Function(T t1, Args... args){
        // Definition
    }

private:
    template<typename T>
    void Function(T t1){
        // Definition
    }
};

#include "header.h"

int main()
{
    Foo foo;
    foo.Function(1, 2, 3, 4, 5);
    return 0;
}

Works just fine. When I try to separate the definition to source.cpp, the gcc starts complaining. I know I have to specialize the templates in order to separate the definition, so I tried adding the code below to the header file:

template<>
void Foo::Function<int, int...>(int t1, int... args);

template<>
void Foo::Function<int>(int);

but without success. What am I missing


edit: gcc error messages:

header.h:15:28: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);

header.h:15:48: error: expansion pattern ‘int’ contains no argument packs void Foo::Function(int t1, int... args);

Upvotes: 1

Views: 284

Answers (2)

Marek R
Marek R

Reputation: 37597

There is better way to do it. First of all it looks like you want to force same type of all arguments (this is done by std::initializer_list in accepted answer). This can be foreced by providing extra explicit argument:

class Foo
{
public:
    template<typename T, typename... Args>
    void Function(T t1, T t2, Args... args)
    {
        LOG;
        this->Function(t1);
        this->Function(t2, args...);
    }

private:
    template<typename T>
    void Function(T t1)
    {
        LOG << VAR(t1);
    }
};

template<>
void Foo::Function<int>(int x)
{
    LOG << " Spec" << VAR(x);
}

As you can see it is enough if you provide specialization of method for single argument.

Live demo

Upvotes: 0

Alecto
Alecto

Reputation: 10740

You can't use int... as a parameter pack, and so this doesn't work. In addition, to separate the source from the definition, you have to fully specify the template, so int... wouldn't work even if that syntax were allowed.

Ways to get around this.

1. Make Function accept an initializer list. We can write function so that it accepts an initializer list of ints:

#include <initializer_list>

class Foo {
   public:
    void Function(int t1, std::initializer_list<int> t2);
};

void Foo::Function(int t1, std::initializer_list<int> t2) {
    for(int i : t2) {
        // stuff
    }
}

Now, you can call Function pretty easily, and it's not even templated:

Foo f; 
f.Function(10, {1, 2, 3, 4, 5});

If there are other places you're using templates, you can expand a parameter pack directly into the initializer list:

template<class... Args>
void invoke_foo(Foo& f, int first, Args... rest) {
    f.Function(first, {rest...}); 
}

2. Use SFINAE to disable all non-int overloads. We can disable all overloads of Foo::Function that don't only accept ints

#include <type_traits>

class Foo {
   public:
    // Requires C++17 for std::conjunction
    // You could write your own std::conjunction too if you'd prefer
    template<class... Args>
    auto Function(int t1, Args... t2)
        -> std::enable_if_t<std::conjunction<std::is_same<Args, int>...>::value>
    {
        // stuff
    }
}; 

The downside to this is that non-integral values won't automatically be converted to int.

Upvotes: 2

Related Questions