当它是左值时如何存储引用,当它是右值时如何存储副本

How to store a reference when it is an lvalue, store a copy when it is an rvalue

我想做一个可以用固定数量的参数调用的工厂函数模板,每个参数类型一个模板参数。有两个参数:

template< typename T1, typename T2 >
and_implementation< T1, T2 > and( T1 && p1, T2 && p2 ){
   return and_implementation< T1, T2 >( p1, p2 );
}

and_implementation 对象中,我想存储对作为左值的每个参数的引用,以及作为右值的每个参数的副本。我不想使用堆。

目标是当我写

auto p1 = ....
auto p2 = ....
auto p3 = and( p1, p3 );

p3 对象仅包含对 p1p2 的引用,但是当我写类似

的内容时
auto p1 = ....
auto p2 = ....
auto p3 = ....
auto p4 = and( p1, and( p2, p3 ));   

p4 对象包含对 p1 的引用,但包含 and(p2, p3).

的副本

有办法吗?

我想到的(工厂叫invert,只有一个参数)是

template< typename T >
struct invert_impl: public gpio {

   T pin;

   template< typename TT > invert_impl( TT && p ):  
      pin( p ) {} // this is line 60

};

template< typename P >
invert_impl< P > invert( P && pin ){
    return invert_impl< P >( pin );
}

这适用于

autp pin4 = lpc_gpio< 4 >{};
auto led = invert( pin4 );

但是

autp pin4 = lpc_gpio< 4 >{};
auto led = invert( invert( pin4 ));

我得到(GCC 4.9.3):

main.cpp:60:14: error: invalid initialization of reference of type 'lpc_gpio<4>&' from expression of type 'invert_impl<lpc_gpio<4>&>'

你想多了。您的构造函数不需要是模板,因为在每个具体的模板实例化中,您已经知道构造函数应该接受的确切类型:它应该接受 T.

template <typename T>
struct invert_impl : public gpio {
  T pin;
  invert_impl(T p) : pin(p) {}
};

您的模板构造函数失败的原因是它也被选为复制或移动构造函数(如果它比隐式生成的复制和移动构造函数更匹配),这是行不通的。复制和移动构造函数采用 const invert_impl &invert_impl &&,不能用于初始化 pin.

注意:从 p 初始化 pin 可能会在此处进行不必要的复制。 std::forward 可以避免这种情况,即使这并不是它最初的目的。

  invert_impl(T p) : pin(std::forward<T>(p)) {}

@Yakk 正确地指出,即便如此,仍然存在一些不必要的操作,可以通过让构造函数采用 T&& 并从 invert 转发来避免它们,如下所示:

template <typename T>
struct invert_impl : public gpio {
  T pin;
  invert_impl(T &&p) : pin(std::forward<T>(p)) {}
};

template <typename T>
invert_impl<T> invert(T &&pin) {
  return invert_impl<T>(std::forward<T>(pin));
}

就因为它圆滑:

template<template<class...>class Z, class...Ts>
Z<Ts...> make( Ts&&... ts ) {
  return {std::forward<Ts>(ts)...};
}

是一个可以像 make<invert_impl>( pin ) 这样调用的函数,它推导出 invert_impl 的类型参数。现在,缺点是它的名声不好。所以我们可以使用一个函数对象:

template<template<class...>class Z,class=void> // =void for SFINAE
struct make {
  template<class...Ts>
  Z<Ts...> operator()(Ts&&...ts)const{
    return {std::forward<Ts>(ts)...};
  }
};

现在我们可以做:

static make<invert_impl> invert;

invert(blah) 做正确的事 (tm),

也是
static make<and_implementation> _and_;

无需重写粘合代码。 (注意:名为 and 的变量或函数实际上是非法的,因为在 C++ 下 and&& 的别名——所以我将我的命名为 _and_)。

现在,当我们添加命名运算符 (bwahaha) 时,这会变得更有趣。

第一个十行库:

namespace named_operator {
  template<class D>struct make_operator{};

  template<class T, char, class O> struct half_apply { T&& lhs; };

  template<class Lhs, class Op>
  half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
    return {std::forward<Lhs>(lhs)};
  }
  template<class Lhs, class Op, class Rhs>
  auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
  -> decltype( invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
  {
    return invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
  }
}    

那么我认为是一个实现:

template<template<class...>class,class...Ts>
std::false_type is_unary(Ts...) { return {}; }
template<template<class>class> std::true_type is_unary() { return {}; }

template<template<class...>class Z>
using unary = decltype( is_unary<Z>() );

template<template<class...>class Z>
struct make<
  Z,std::enable_if_t<!unary<Z>{}>
>:named_operator::make_operator<make<Z>> {
  template<class...Ts>
  Z<Ts...> operator()(Ts&&...ts)const{
    return {std::forward<Ts>(ts)...};
  }
  template<class Lhs, class Rhs>
  friend Z<Lhs, Rhs> invoke( Lhs&& lhs, make<Z> m, Rhs&& rhs ) {
    return m(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs));
  }
};

这给了我们

auto r = p1 *_and_* p2;

作为

的替代品
auto r = _and_(p1, p2);

这很有趣。 (假设我点了所有的 is 并穿过了上面的所有 ts)