如何为具有特定方法名称的类型专门化模板化函数?

How do I specialize a templated function for types that have a particular method name?

提问人:Harry Williams 提问时间:6/13/2023 最后编辑:Harry Williams 更新时间:6/13/2023 访问量:95

问:

第三方库为我们提供了一个看起来像这样的函数(显然实际函数要复杂得多):

template<typename T>
std::string toString(const T& value) {
    std::cout << "Called 'unspecialized' toString" << std::endl;
    return std::to_string(value);
}

由于这是来自第三方库,因此我无法更改此函数签名。

我们的许多内部类型都有一个方法,例如:toString

struct Foo {
    std::string toString() const {
        return "Foo";
    }
};

理想情况下,调用函数将遵循内部类型的方法(如果有)。我尝试使用 SFINAE 实现此目的:toStringtoString

template<typename T, typename Enable = decltype(std::declval<T>().toString())>
std::string toString(const T& value) {
    std::cout << "Called 'specialized' toString" << std::endl;
    return value.toString();
}

但是,现在调用此类类型会导致编译错误:toString

int main() {
    toString(5); // OK
    toString(Foo{}); // compilation error: call of overloaded 'toString(Foo)' is ambiguous
}

如何在不更改 的非专用版本的情况下解决此错误?toString

编辑:对于某些背景,这是 Google Test 函数的问题,该函数从单元测试(doc)中打印出值。该函数旨在查找特定类型的重载。如果找到一个,它将使用该重载来打印类型。如果它没有找到一个,它会使用其默认(“非专用”)实现,它只会转储字节。PrintTo

有几个答案提供了需要更改函数默认实现的解决方案。这些答案可能对其他用户有所帮助,但对这种情况没有帮助。我将 Paul 基于概念的答案标记为已接受,因为它似乎是唯一一个不需要修改默认签名的答案,即使它需要 C++20。

我认为这个问题可能更准确地归类为 Google Test 的限制。 我在 GitHub 中发现了至少一个问题可以解决这个问题,但它仍然悬而未决。

C++ sfinae 模板专用化 参数依赖查找

评论

0赞 BoP 6/13/2023
就像错误消息所说的那样,您不是在专门化函数,而是在添加重载。尝试部分专用化函数模板会遇到一些问题:为什么函数模板不能部分专用化?
0赞 Paul Sanders 6/13/2023
你能使用 C++20 吗?
1赞 Harry Williams 6/13/2023
嗨,保罗,我正在寻找C++17解决方案。Concepts 可能会解决这个问题,但我们距离升级编译器还有 1+ 年的时间。
0赞 Harry Williams 6/13/2023
嗨,BoP,也许我的措辞可以更清楚。当我说“specialize”时,我只是说“为这种类型调用正确的函数”。我知道不允许对功能进行部分专业化。
1赞 Paul Sanders 6/13/2023
对不起,哈利,刚刚看到你的评论。我现在把答案留在那里;对不起,它没有做你想做的。也许你可以说服团队继续编译器......

答:

2赞 Paul Sanders 6/13/2023 #1

在 C++20 中,这很容易。只需将您的 SFINAE 内容替换为以下内容:

template <typename T>
concept HasToString = requires(T t) { t.toString(); };

template<typename T> requires HasToString <T>
std::string toString(const T& value){
    std::cout << "Called 'specialized' toString" << std::endl;
    return value.toString();
}

现场演示


另请参阅评论中的@TedLyngmo(更好的)产品。


这是我对 C++17 解决方案的尝试。

把问题转过来,用它替换你的SFINAE的东西(从来都不是我最喜欢的东西之一),以便只为算术类型选择内置的(调整测试以适应)。 使它特别整洁:to_stringif constexpr

template<class T>
std::string toString (const T& value)
{
    if constexpr (std::is_arithmetic_v <T>)
    {
        std::cout << "Called 'unspecialized' toString" << std::endl;
        return std::to_string (value);
    }
    else
    {
        std::cout << "Called 'specialized' toString" << std::endl;
        return value.toString ();
    }
}

现场演示

评论

3赞 Ted Lyngmo 6/13/2023
也许 std::string toString(HasToString auto const& value) 会更容易?
1赞 Paul Sanders 6/13/2023
智能工作,@ted
0赞 Harry Williams 6/13/2023
在启用 C++20 的情况下测试了此解决方案,它就像一个魅力!如果真的没有其他方法可以在没有概念的情况下做到这一点,我会将这个答案标记为已接受。
0赞 Paul Sanders 6/13/2023
@HarryWilliams找到了:)的方法
1赞 Ted Lyngmo 6/13/2023
@HarryWilliams 3PP 函数(应该)位于命名空间中。您可以将 your 放在不同的命名空间中,并且只使用该函数。如果有 ,则调用它,否则调用 3PP 版本。toStringtoStringTT::toString()