繁体   English   中英

C++17 中使用 SFINAE 和模板的“std::ostream”和“<<”运算符的回退

[英]Fallback for "std::ostream" and "<<" operator using SFINAE and templates in C++17

我在内部使用 Catch2 和TEST_CASE块 我有时为了方便起见声明本地临时struct 有时需要显示这些struct ,为此 Catch2 建议使用std::ostream实现<<运算符。 不幸的是,使用仅限本地的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;
}

我有几个问题:

  • decltype技巧是现代的 C++ 还是我们可以使用<type_traits>来实现同样的效果?
  • 有没有办法要求toString()返回值是一个std::string从而禁用模板替换?
  • 是否保证具有operator<<具体实现的 class 优先于模板(如果存在)?

此外,我发现这个解决方案非常脆弱(尽管这个简单的片段有效,但在编译我的整个项目时我会出错),而且我认为由于其隐含的性质,它可能会导致错误。 不相关的类可能会定义toString()方法而不期望它被用于<<模板替换。

我认为使用基数 class 然后使用 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;
}

这个解决方案的缺点是我不能将toString()定义为基类 class 中的virtual方法,否则它会阻止聚合初始化(这对我的测试用例非常有用)。 因此, WithToString只是一个空struct ,用作std::enable_if的“标记”。 它本身不会带来任何有用的信息,需要文档才能正确理解和使用。

您对第二种解决方案有何看法? 这可以以某种方式改进吗?

我的目标是 C++17,所以不幸的是我还不能使用<concepts> 我也想避免使用<experimental> header(尽管我知道它包含对 C++17 有用的东西)。

您可以将这两种方法视为“具有某些属性的所有类型上的operator<< ”。

第一个属性是“有一个toString() ”方法(甚至可以在 C++11 中工作。这仍然是 SFINAE,在这种情况下,替换在返回类型中)。 您可以检查toString()是否返回具有不同 SFINAE 样式的std::string

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

在这个模板之前总是会选择一个非模板operator<< 在此之前,还将选择一个更“专业”的模板。 重载决议的规则有点复杂,但可以在这里找到: https://en.cppreference.com/w/cpp/language/overload_resolution#Best_viable_function

第二个属性是“派生自WithToString ”。 如您所料,这个更“明确”,并且更难意外/意外地使用operator<<

您实际上可以与朋友 function 一起定义内联运算符:

struct A {
    std::string toString() const {
      return "A";
    }
    friend std::ostream& operator<<(std::ostream& os, const A& a) {
        return os << a.toString();
    }
};

你也可以在WithToString中有这个朋友声明,使它成为一个自我记录的混合

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";
    }
};

std::tie 用于 C++17 结构运算符<!--?</div--><div id="text_translate"><p> 考虑以下 C++17 结构:</p><pre> struct S { M1 m1; M2 m2; M3 m3; bool operator<(const S& that) const { return tie() < that.tie(); } auto tie() const { return std::tie(m1,m2,m3); } };</pre><p> 这个对吗? S::tie会返回成员引用的元组,还是会复制一份? 会自动推断出正确的类型(引用元组)吗? constness 做正确的事吗?</p><p> (我看到的示例对 std::tie 进行了两次调用,并且没有像这样分解为单独的成员 function。想知道/怀疑是否有原因。)</p></div>

[英]std::tie for C++17 struct operator<?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM