具有可变参数模板参数的函数重载

时间:2018-07-26 15:39:14

标签: c++ c++11

我有一些代码要从使用重载函数转换为使用可变参数模板参数。

现有代码具有用于不同参数计数的多个模板化功能:

#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"));
}

这将输出:

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

如果我尝试用单个可变参数模板版本替换两个模板化的Construct()函数,那么我将无法确定应如何仅为Foo类指定特殊版本(在GCC上尝试4.9.2、8.1和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};
}

编译器尝试使用可变参数模板版本失败(因此CreateName中没有Foo)。一样:

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&>(const std::string& name)
{
   return Foo{"ID:" + name};
}

我可以通过制作通用模板并使用std::enable_if_t来限制类型来使其工作:

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

哪个不像原始的template<> Foo Construct<Foo>(...)版本那样容易阅读。将std::enable_if_t与可变参数模板一起使用是唯一的选择,还是有某种方法可以使期望的行为超载?

3 个答案:

答案 0 :(得分:2)

这里的问题是专业化必须与基本模板匹配。

您的基本模板将被推导为Construct<Foo, std::string>,并且您的专业化必须匹配此模板才能被视为有效。

如果您将专业化更改为

template<>
Foo Construct<Foo, std::string>(std::string name)
{
    return Foo{"ID:" + name};
}

gcc上可以正常使用。

答案 1 :(得分:1)

不要专门研究功能模板。最简单的方法是只使用类模板:

template <typename T, typename... Args>
T Construct(Args&&... args) {
    return ConstructorImpl<T>::apply(std::forward<Args>(args)...);
}

这使得直接了解如何专长:

template <typename T>
struct ConstructorImpl {
    template <typename... Args>
    static T apply(Args&&... args) {
        return T(T::CreateName(std::forward<Args>(args)...));
    }
};

template <>
struct ConstructImpl<Foo> {
    static Foo apply(std::string const& name) {
        return Foo("ID:" + name});
    }
};

使用此方法自然会向Construct应用ConstructorImpl<T>::apply可调用Args...的约束。就像这样简单:

template <typename T, typename... Args>
auto Construct(Args&&... args)
    -> decltype(ConstructorImpl<T>::apply(std::forward<Args>(args)...))
{
    return ConstructorImpl<T>::apply(std::forward<Args>(args)...);
}

(尽管授予的权限不会检查它是否实际上返回了T

答案 2 :(得分:0)

问题是您不能部分专门化模板功能。

我建议从模板struct传递模板(或者在Boo专业化中不使用)方法func()

模板

struct Constr
 {
   template <typename ... Args>
   static T func (Args const & ... names)
    { return T(T::CreateName(names...)); }
 };

template <>
struct Constr<Foo>
 {
   static Foo func (std::string const & name)
    { return Foo{"ID:" + name}; }
 };

通话变为

Constr<Boo>::func(std::string("123"));
Constr<Foo>::func(std::string("456"));