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

huangapple go评论77阅读模式
英文:

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

问题

以下是您要翻译的内容:

A third-party library provides us with a function that looks something like this (obviously the actual function is much more complicated):

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

Because this is from a third-party libary, I am unable to change this function signature.

Many of our internal types have a toString method, for example:

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

Ideally, calling the toString function would defer to the internal type's toString method if it has one. I have tried to accomplish this using SFINAE:

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

However, calling toString for such types now results in a compilation error:

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

How do I resolve this error without changing the unspecialized version of toString?

Edit: for some background, this is an issue with Google Test's PrintTo function, which prints out values from unit tests (doc). That function is designed to look for an overload for a particular type. If it finds one, it uses that overload to print the type. If it doesn't find one, it uses its default ('unspecialized') implementation, which just dumps out the bytes.

There were several answers that offered solutions which required changing the default implementation of the function. These answers may be helpful to other users, but they do not help this case. I marked Paul's concept-based answer as accepted since it seems to be the only one that works without requiring modification of the default signature, even though it requires C++20.

I think this issue may be more accurately categorized as a limitation with Google Test. I found at least one issue in GitHub that addresses this, but it's still open.

英文:

A third-party library provides us with a function that looks something like this (obviously the actual function is much more complicated):

template&lt;typename T&gt;
std::string toString(const T&amp; value) {
    std::cout &lt;&lt; &quot;Called &#39;unspecialized&#39; toString&quot; &lt;&lt; std::endl;
    return std::to_string(value);
}

Because this is from a third-party libary, I am unable to change this function signature.

Many of our internal types have a toString method, for example:

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

Ideally, calling the toString function would defer to the internal type's toString method if it has one. I have tried to accomplish this using SFINAE:

template&lt;typename T, typename Enable = decltype(std::declval&lt;T&gt;().toString())&gt;
std::string toString(const T&amp; value) {
    std::cout &lt;&lt; &quot;Called &#39;specialized&#39; toString&quot; &lt;&lt; std::endl;
    return value.toString();
}

However, calling toString for such types now results in a compilation error:

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

How do I resolve this error without changing the unspecialized version of toString?

Edit: for some background, this is an issue with Google Test's PrintTo function, which prints out values from unit tests (doc). That function is designed to look for an overload for a particular type. If it finds one, it uses that overload to print the type. If it doesn't find one, it uses its default ('unspecialized') implementation, which just dumps out the bytes.

There were several answers that offered solutions which required changing the default implementation of the function. These answers may be helpful to other users, but they do not help this case. I marked Paul's concept-based answer as accepted since it seems to be the only one that works without requiring modification of the default signature, even though it requires C++20.

I think this issue may be more accurately categorized as a limitation with Google Test. I found at least one issue in GitHub that addresses this, but it's still open.

答案1

得分: 2

In 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_string(根据需要调整该测试以适应)if 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 ();
    }
}

在线演示

英文:

In C++20 this is easy. Just replace your SFINAE stuff with this:

template &lt;typename T&gt;
concept HasToString = requires(T t) { t.toString(); };

template&lt;typename T&gt; requires HasToString &lt;T&gt;
std::string toString(const T&amp; value){
    std::cout &lt;&lt; &quot;Called &#39;specialized&#39; toString&quot; &lt;&lt; std::endl;
    return value.toString();
}

Live demo


See also @TedLyngmo's (better) offering in the comments.


Here's my attempt at a C++17 solution.

Turn the problem on its head and replace your SFINAE stuff (never one of my favourite things) with this, in order to select the built-in to_string for arithmetic types only (tweak that test to suit). if constexpr makes this particularly neat:

template&lt;class T&gt;
std::string toString (const T&amp; value)
{
    if constexpr (std::is_arithmetic_v &lt;T&gt;)
    {
        std::cout &lt;&lt; &quot;Called &#39;unspecialized&#39; toString&quot; &lt;&lt; std::endl;
        return std::to_string (value);
    }
    else
    {
        std::cout &lt;&lt; &quot;Called &#39;specialized&#39; toString&quot; &lt;&lt; std::endl;
        return value.toString ();
    }
}

Live demo

huangapple
  • 本文由 发表于 2023年6月13日 04:29:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460117.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定