jeudi 26 juillet 2018

Overloaded functions with variadic template arguments

I have some code that I want to convert from using overloaded functions to using variadic template arguments.

The existing code has multiple templated functions for the different parameter counts:

#include <iostream>
#include <string>

struct Boo
{
    Boo(const std::string& name)
    {
        std::cout << "Boo constructor: " << name << std::endl;
    };   
    static std::string CreateName(const std::string& name)
    {
        return "BooID:" + name;   
    }
};

struct Foo
{
    Foo(const std::string& name)
    {
        std::cout << "Foo constructor: " << name << std::endl;
    };
};

template <typename T>
T Construct(const std::string& name)
{
   return T(T::CreateName(name));
}

template <typename T>
T Construct(const std::string& name1, const std::string& name2)
{
   return T(T::CreateName(name1, name2));
}

// Class Foo doesn't have CreateName available
template<>
Foo Construct<Foo>(const std::string& name)
{
    return Foo{"ID:" + name};
}

int main()
{
    Construct<Boo>(std::string("123"));
    Construct<Foo>(std::string("456"));
}

This outputs:

Boo constructor: BooID:123
Foo constructor: ID:456

If I try to replace the two templated Construct() functions with a single variadic templated version then I can't work out how I should specify the special version just for the Foo class (tried on GCC 4.9.2, 8.1 and Visual Studio 2015).

template <class T, typename ...Args>
T Construct(Args... args)
{
   return T(T::CreateName(args...));
}

// Class Foo needs some extra information when constructed with strings...
template<>
Foo Construct<Foo>(const std::string& name)
{
    return Foo{"ID:" + name};
}

Fails with the compiler trying to use the variadic template version (so no CreateName in Foo).

I can get it to work by making a generic template and using std::enable_if_t to restrict the types:

template <class T, typename ...Args>
T Construct(Args... args)
{
   return T(T::CreateName(args...));
}

// Class Foo needs some extra information when constructed with strings...
template<typename T, typename = std::enable_if_t<std::is_base_of<Foo, T>::value>>
T Construct(const std::string& name)
{
    return T{"ID:" + name};
}

Which isn't as easy to read as the original template<> Foo Construct<Foo>(...) version. Is using std::enable_if_t with variadic template the only option, or is there some way of overloading that will give the desired behaviour?

Aucun commentaire:

Enregistrer un commentaire