如何在左值为零时存储引用,在副本为左值时存储副本

时间:2015-02-26 13:18:59

标签: c++ templates

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

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)的副本。

有办法做到这一点吗?

我想出的是(工厂被称为反转并且只有一个参数)

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>&>'

2 个答案:

答案 0 :(得分:1)

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

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

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

注意:从pin初始化p可能会在此处制作不必要的副本。 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));
}

答案 1 :(得分:0)

仅仅因为它很光滑:

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

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