【问题标题】:Generating function signatures for rvalue/lvalue refs with metaprogramming?使用元编程为右值/左值引用生成函数签名?
【发布时间】:2018-08-13 14:43:18
【问题描述】:

假设我想将右值和左值都传递给我的函数,但我不想编写所有 O(2n) 函数签名,其中 n 是参数的数量。例如,对于我的字符串附加函数:

inline static String append(String&& l1, String&& l2) {
    l1.append(l2);
    return l1;
}
inline static String append(String&& l1, String& l2) {
    l1.append(std::forward<String>(l2));
    return l1;
}
inline static String append(String& l1, String&& l2) {
    l1.append(l2);
    return l1;
}
inline static String append(String &l1, String& l2) {
    return append(std::forward<String>(l1), std::forward<String>(l2));
}

函数签名太多了! TMP 是图灵完备的语言,必须有一种方法可以在编译时生成同一事物的所有 4 个版本,并且正确使用 std::forward 对吗? C++17 魔术加分。

【问题讨论】:

  • 你在这里误用了forward
  • 更具体地说,在您的情况下,它充当std::move。它旨在与通用引用一起使用...
  • std::forward 不是 std::move 还是 no-op 吗?我怎么会误用它?
  • 是的。但是由于您不使用通用引用,因此在您的情况下它总是会移动。

标签: c++ c++17


【解决方案1】:

类似这样的:

// this will be in C++20
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename L=String, typename R=String,
    std::enable_if_t<
        std::is_same_v<std::remove_reference_t<L>, String> &&
        std::is_same_v<remove_cvref_t<R>, String>,
        int> = 0>
inline static String append(L&& l, R&& r) {
    l.append(std::forward<R>(r));
    return std::forward<L>(l);
}

默认模板参数用于允许 braced-init-list 之类的东西。不同的约束是因为R 可以是对const String 的某种引用,但L 不能。


根据您的预期用途,这可能并不完全正确,因为它会阻止从String 传入类型派生 - 而原始重载则不会。如果你不关心这个,那么这很好。如果你这样做了,那么你想检查is_base_of

std::is_base_of_v<String, remove_cvref_t<L>> &&
std::is_base_of_v<String, remove_cvref_t<R>> &&
!std::is_const_v<std::remove_reference_t<L>>

除此之外,您可以只限制 append 本身的格式:

template <typename L=String, typename R=String,
    typename = decltype(std::declval<L&>().append(std::declval<R>())),
    std::enable_if_t<std::is_convertible_v<L, String>, int> = 0>
inline static String append(L&& l, R&& r) { ... }

【讨论】:

  • 继承不是问题,事实上就像我在 cmets 中所说的,我想要 String、String& 和 String&& 的严格类型。我真的很喜欢你的最后一个技巧,即在编译时推测性地调用 append!
  • 总而言之,你必须在完美转发和功能多态性之间做出选择,这真是太糟糕了。我希望 C++20 能以更自然的方式解决这个问题。
  • @rausted 我不确定您所说的“功能多态性”是什么意思,或者这里有什么不好的地方?
  • 如果你有一个append(String a, String b) 和一个append(List&lt;int&gt; a, List&lt;int&gt; b) 函数多态允许根据类型签名和参数找到正确的。到目前为止,这对于通用引用是不可能的,因为T&amp;&amp; 是一个通用的,我们必须使用 TMP 黑客来做到这一点,就像你答案中的那个一样。我宁愿使用多态而不是 SFINAE。
【解决方案2】:

您可以自动推断引用类型:

template<typename Str>
std::remove_reference_t<Str> append(Str &&, Str &&);

如果你希望它完全是String,那么

template<typename Str>
std::enable_if_t<
        std::is_same_v<std::remove_reference_t<Str>, String>
        , String>
append(Str &&, Str &&);

至少它不是二次的。很少有 typedef 可以压缩整个事情。

【讨论】:

  • 这还不够通用,它不能接受const String&amp;String
【解决方案3】:

你可以去

template<class T, class S> static inline auto append(T&& l1, S&& l2) {
    l1.append(std::forward<S>(l2));

    return std::forward<T>(l1);
}

请注意,当实例化失败时,错误消息并不容易掌握。这可以通过适当约束模板来缓解。

【讨论】:

  • 嗯,我记得看到 T&& 特别神奇,所以不可能将我的论点专门化为字符串并获得完美的转发?
  • 你真的应该限制模板。
猜你喜欢
  • 2017-12-03
  • 1970-01-01
  • 1970-01-01
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-27
相关资源
最近更新 更多