Del*_*gan 3 c++ sfinae type-traits template-meta-programming c++17
我将 Catch2 与TEST_CASE内部块一起使用struct,为了方便起见,我有时声明本地临时。有时需要显示这些内容struct,为此,Catch2 建议使用 来实现<<运算符std::ostream。不幸的是,用 local-only 实现这变得非常复杂struct,因为这样的运算符不能内联定义,也不能在TEST_CASE块中定义。
我想到了一个可能的解决方案,即定义一个模板,如果该方法存在,<<则将调用该模板:toString()
#include <iostream>
#include <string>
template <typename T>
auto operator<<(std::ostream& out, const T& obj) -> decltype(obj.toString(), void(), out)
{
out << obj.toString();
return out;
}
struct A {
std::string toString() const {
return "A";
}
};
int main() {
std::cout << A() << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我有几个问题:
decltype现代 C++ 还是我们可以使用它<type_traits>来实现相同的效果?toString()返回值为 astd::string从而禁用模板替换?operator<<将优先于模板(如果存在)?另外,我发现这个解决方案非常脆弱(尽管这个简单的代码片段有效,但在编译整个项目时我遇到了错误),而且我认为由于其隐含的性质,它可能会导致错误。不相关的类可能会定义toString()方法而不期望在模板替换中使用它<<。
我认为使用基类然后使用 SFINAE 显式地执行此操作可能会更清晰:
#include <iostream>
#include <string>
#include <type_traits>
struct WithToString {};
template <typename T, typename = std::enable_if_t<std::is_base_of_v<WithToString, T>>>
std::ostream& operator<<(std::ostream& out, const T& obj)
{
out << obj.toString();
return out;
}
struct A : public WithToString {
std::string toString() const {
return "A";
}
};
int main() {
std::cout << A() << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
该解决方案的缺点是我无法在基类中定义toString()为virtual方法,否则它会阻止聚合初始化(这对我的测试用例非常有用)。因此,WithToString只是一个空的struct,充当 的“标记” std::enable_if。它本身不会带来任何有用的信息,并且需要文档才能正确理解和使用。
您对第二个解决方案有何看法?这可以以某种方式改进吗?
我的目标是 C++17,所以不幸的是我还不能使用<concepts>。另外我想避免使用<experimental>标头(尽管我知道它包含对 C++17 有用的东西)。
您可以将这两种方法视为“operator<<在具有某些属性的所有类型上”。
第一个属性是“has a toString()”方法(甚至可以在 C++11 中使用。这仍然是 SFINAE,在这种情况下,替换位于返回类型中)。您可以让它检查toString()返回std::string具有不同风格的 SFINAE:
template <typename T, std::enable_if_t<
std::is_same_v<std::decay_t<decltype(std::declval<const T&>().toString())>, std::string>,
int> = 0>
std::ostream& operator<<(std::ostream& out, const T& obj)
{
out << obj.toString();
return out;
}
Run Code Online (Sandbox Code Playgroud)
operator<<并且始终会在该模板之前选择非模板。在此之前还会选择一个更“专业”的模板。重载解析的规则有点复杂,但可以在这里找到:https ://en.cppreference.com/w/cpp/language/overload_resolution#Best_viable_function
第二个属性是“源自WithToString”。正如您所猜测的,这个更加“明确”,并且更难意外/意外地使用operator<<.
您实际上可以使用友元函数内联定义运算符:
struct A {
std::string toString() const {
return "A";
}
friend std::ostream& operator<<(std::ostream& os, const A& a) {
return os << a.toString();
}
};
Run Code Online (Sandbox Code Playgroud)
你也可以在 中包含这个朋友声明WithToString,使其成为一个自记录的mixin
template<typename T> // (crtp class)
struct OutputFromToStringMixin {
friend std::ostream& operator<<(std::ostream& os, const T& obj) {
return os << obj.toString();
}
};
struct A : OutputFromToStringMixin<A> {
std::string toString() const {
return "A";
}
};
Run Code Online (Sandbox Code Playgroud)