简体   繁体   中英

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

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 int s:

#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 int s

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM