具有不可更改的通用参考函数模板的 C++ 重载解决方案
C++ Overload resolution with universal reference function template which can't be changed
假设我的代码中某处是一个带有通用参考参数的函数 foo
,我无法更改:
template<typename T>
auto foo(T&& t) { std::cout<<"general version"<<std::endl; }
现在我想为给定的 class A
重载 foo
,并确保为 A
的任何限定符和引用类型调用重载。为此,我可以强行为所有可能的资格提供重载(暂时忽略 volatile
):
auto foo(A & a) { std::cout<<"A&"<<std::endl; }
auto foo(A const& a) { std::cout<<"A const&"<<std::endl; }
auto foo(A && a) { std::cout<<"A &&"<<std::endl; }
auto foo(A const&& a) { std::cout<<"A const&&"<<std::endl; }
Demo。然而,对于更多参数,这非常糟糕。
或者,我可以按值传递,这似乎也捕获了之前的所有情况:
auto foo(A a) { std::cout<<"A"<<std::endl; }
Demo。但是现在需要复制大对象(至少原则上是这样)。
有解决这些问题的优雅方法吗?
记住万能参考函数我是改不了的,所以SFINAE之类的是不可能的
C++20 的更新:下面的答案对于 C++11 到 C++17 仍然适用,但在 C++20 中你可以这样做:
template <typename T>
requires std::same_as<std::remove_cvref_t<T>, A>
auto foo(T&& t) {
// since this is more constrained than the generic forwarding reference
// this one should be preferred for foo(A{})
}
您可以通过创建命名概念来使用更方便的语法:
template <typename T, typename U>
concept DecaysTo = std::same_as<std::decay_t<U>, T>;
// longest form
template <typename T> requires DecaysTo<T, A> void foo(T&&);
// middle form
template <DecaysTo<A> T> void foo(T&&);
// abbreviated form
void foo(DecaysTo<A> auto&&);
老实说,我觉得你运气不好。典型的方法都失败了。你可以做...
SFINAE?
template <typename T> auto foo(T&& );
template <typename T,
typename = only_if_is<T, A>>
auto foo(T&& );
foo(A{}); // error: ambiguous
写一个 class 接受左值或右值引用?
template <typename T> lref_or_ref { ... };
template <typename T> auto foo(T&& );
auto foo(lref_or_ref<A> );
foo(A{}); // calls general, it's a better match
您最好的办法是使用选择器引入转发功能:
template <int I> struct chooser : chooser<I - 1> { };
template <> struct chooser<0> { };
template <typename T>
auto bar(T&& t, chooser<0> ) {
// worst-option, general case
foo(std::forward<T>(t));
}
template <typename T,
typename = only_if_is<T, A>>
auto bar(T&& t, chooser<1>) {
// A-specific
}
template <typename T>
auto bar(T&& t) {
bar(std::forward<T>(t), chooser<20>{});
}
但是您提到 这对您也不起作用。
假设我的代码中某处是一个带有通用参考参数的函数 foo
,我无法更改:
template<typename T>
auto foo(T&& t) { std::cout<<"general version"<<std::endl; }
现在我想为给定的 class A
重载 foo
,并确保为 A
的任何限定符和引用类型调用重载。为此,我可以强行为所有可能的资格提供重载(暂时忽略 volatile
):
auto foo(A & a) { std::cout<<"A&"<<std::endl; }
auto foo(A const& a) { std::cout<<"A const&"<<std::endl; }
auto foo(A && a) { std::cout<<"A &&"<<std::endl; }
auto foo(A const&& a) { std::cout<<"A const&&"<<std::endl; }
Demo。然而,对于更多参数,这非常糟糕。
或者,我可以按值传递,这似乎也捕获了之前的所有情况:
auto foo(A a) { std::cout<<"A"<<std::endl; }
Demo。但是现在需要复制大对象(至少原则上是这样)。
有解决这些问题的优雅方法吗?
记住万能参考函数我是改不了的,所以SFINAE之类的是不可能的
C++20 的更新:下面的答案对于 C++11 到 C++17 仍然适用,但在 C++20 中你可以这样做:
template <typename T>
requires std::same_as<std::remove_cvref_t<T>, A>
auto foo(T&& t) {
// since this is more constrained than the generic forwarding reference
// this one should be preferred for foo(A{})
}
您可以通过创建命名概念来使用更方便的语法:
template <typename T, typename U>
concept DecaysTo = std::same_as<std::decay_t<U>, T>;
// longest form
template <typename T> requires DecaysTo<T, A> void foo(T&&);
// middle form
template <DecaysTo<A> T> void foo(T&&);
// abbreviated form
void foo(DecaysTo<A> auto&&);
老实说,我觉得你运气不好。典型的方法都失败了。你可以做...
SFINAE?
template <typename T> auto foo(T&& );
template <typename T,
typename = only_if_is<T, A>>
auto foo(T&& );
foo(A{}); // error: ambiguous
写一个 class 接受左值或右值引用?
template <typename T> lref_or_ref { ... };
template <typename T> auto foo(T&& );
auto foo(lref_or_ref<A> );
foo(A{}); // calls general, it's a better match
您最好的办法是使用选择器引入转发功能:
template <int I> struct chooser : chooser<I - 1> { };
template <> struct chooser<0> { };
template <typename T>
auto bar(T&& t, chooser<0> ) {
// worst-option, general case
foo(std::forward<T>(t));
}
template <typename T,
typename = only_if_is<T, A>>
auto bar(T&& t, chooser<1>) {
// A-specific
}
template <typename T>
auto bar(T&& t) {
bar(std::forward<T>(t), chooser<20>{});
}
但是您提到