C++模板:专用成员函数,用于解决主模板中不明确的重载情况



我有一个模板,其中函数被重载,因此它可以处理std::string参数和模板实例化的参数类型。这工作正常,除非模板是用 std::string 实例化的,因为这会导致两个具有相同原型的成员函数。因此,我选择专门针对这种特殊情况使用该功能。然而,似乎编译器(g++ 4.8.1带有标志-std=c++0x(从未达到专业化实际上覆盖主模板的地步,并且在它似乎意识到它应该使用专业化之前抱怨模棱两可的重载。有没有办法解决这个问题?

#include <iostream>
template<class T>
struct A {
    std::string foo(std::string s) { return "ptemplate: foo_string"; }
    std::string foo(T e) { return "ptemplate: foo_T"; }
};
template<> //Error!
std::string A<std::string>::foo(std::string s) { return "stemplate: foo_string"; }
int main() {
    A<int> a; //Ok!
    std::cout << a.foo(10) << std::endl; 
    std::cout << a.foo("10") << std::endl;
    //A<std::string> b; //Error!
    //std::cout << a.foo("10") << std::endl;
    return 0;
}

这会导致编译错误,即使我根本不使用 std::string 实例化(似乎编译器在看到专用化后立即使用 std::string 实例化,并且在实际处理专用化之前,它抱怨专用化反过来将"消除歧义"的模棱两可的重载(。

编译器输出:

p.cpp: In instantiation of 'struct A<std::basic_string<char> >':
p.cpp:10:27:   required from here
p.cpp:6:14: error: 'std::string A<T>::foo(T) [with T = std::basic_string<char>; std::string = std::basic_string<char>]' cannot be overloaded
  std::string foo(T e) { return "ptemplate: foo_T"; }
              ^
p.cpp:5:14: error: with 'std::string A<T>::foo(std::string) [with T = std::basic_string<char>; std::string = std::basic_string<char>]'
  std::string foo(std::string s) { return "ptemplate: foo_string"; }
              ^

我希望它只是跳过主模板中foo()的实现并使用专业化,而不考虑主模板foo().是否可以以某种方式完成,也许使用非类型模板参数,或者我是否必须为std::string制作一个完全专用的类模板,其中包含它所暗示的所有代码重复(我不想在这里使用继承(......其他建议?

当你指定你的成员函数时,你仍然得到双重不明确的声明。你需要的是专门化结构模板:

template<>
struct A<std::string> {
    std::string foo(std::string s) { return "ptemplate: foo_string"; }
};

如果A结构有很多成员,也许你可以重构:

template<typename T>
struct Afoo
{
    std::string foo(T s) { ... }
    std::string foo(std::string s) { ... }
};
template<>
struct Afoo<std::string>
{
    std::string foo(std::string s) { ... }
};
template<typename T>
struct A : Afoo<T>
{
    //a lot of code
};

我将自己回答这个问题,因为我今天一直在深入研究这个主题,我认为这些解决方案很好。到目前为止,所有其他职位都是有贡献的,并且具有在其他情况下具有潜力的有吸引力的细节。但是,我更喜欢在考虑以下几点的情况下这样做:

  • 避免使用多个类模板
  • 尽可能避免过于复杂的专业化
  • 避免使用继承和重构到基类和派生类中
  • 避免使用额外的包装器

在我接受它作为我的答案之前,请随时发表评论。

关于该主题的另一个很好的和鼓舞人心的帖子,重点是使用成员函数重载而不是专业化,可以在模板类成员函数的显式专用化中找到

解决方案 1

template<class T>
struct A {
    template<class V = T> std::string foo(T) { return "foo_T"; }
    std::string foo(std::string) { return "foo_std::string"; }
    std::string foo(const char *) { return "foo_const char *"; }
};
template<> template<> 
std::string A<std::string>::foo(std::string s) { return foo(s); }

我认为这是一个密集且易于理解的解决方案,允许所有类实例化使用 foo(std::string)foo(const char *)(用于将字符串作为右值传递(。使用虚拟模板参数可以有效地阻止具有std::string的类实例化导致不明确的重载,同时实际的模板参数会阻碍具有不可预测的函数参数的不受控制的函数实例化。唯一的问题可能来自带有std::string的类实例化,如果使用foo<std::string>(std::string)显式调用,则可能会使用模板而不是常规成员函数,我希望类以哪种方式使用常规foo(std::string)而不是函数模板进行其他实例化。此问题可通过使用单个模板专用化来解决。

解决方案 2

template<class T>
struct A {
        template<class V> std::string foo(V s) { return foo_private(s); } 
    private:
        template<class V = T> std::string foo_private(T) { return "foo_T"; }
        std::string foo_private(const char *) { return "foo_const char *"; }
        std::string foo_private(std::string) { return "foo_std::string"; }
};

这个版本允许我们跳过专用化,以便在类声明中使用第二个模板。

两个版本都用于:

int main() {
    A<int> a;
    std::cout << a.foo(10) << std::endl; 
    std::cout << a.foo("10") << std::endl;
    A<std::string> b;
    std::cout << b.foo<std::string>("10") << std::endl;
    std::cout << b.foo("10") << std::endl;
    return 0;
}

。输出:

foo_T
foo_const char *
foo_const char *
foo_std::string

错误是说您最终创建了两个具有相同签名的方法。这是因为该结构已使用 std::string 作为参数进行模板化。

您应该使用自己的模板参数"K"将函数作为模板化函数,与结构模板参数"T"无关。然后,您可以仅实现函数的模板专用化。

我承认我在下面提供的解决方案确实是一个黑客解决方案,但它确实完成了您要做的事情,而且有点有趣。请在使用之前彻底考虑一下;-(

我通过创建一个名为 FakeType 的新类型来解决此问题,该类型可以从您的模板类型T构造。foo的第二个重载现在是用于FakeType<T>而不是T,所以即使T == string也会有两种不同的重载:

template <typename T>
struct FakeType
{
    T t;
    FakeType(T const &t_): t(t_) {}
    operator T() { return t; }
};
template <typename T>
struct A
{
    string foo(string s) { return "ptemplate: foo_string"; }
    string foo(FakeType<T> e) { return "ptemplate: foo_T"; }
};

对于T != string的情况:

A<int>().foo("string"); // will call foo(string s)
A<int>().foo(1); // will call foo(FakeType<int> e)

在后一种情况下,int将被提升为FakeType<int>,可以通过转换运算符用作常规int

对于T == string的情况:

A<string>().foo("string"); // will still call foo(string s)

因为编译器始终更喜欢不需要提升的重载。

附言。此方法假定foo将按值或常量引用获取其参数。一旦您尝试通过引用传递,它就会中断(这可以修复(。

相关内容

最新更新