Reputation: 1039
I want to make a factory function template that can be called with a fixed number of parameters, each parameter type a template parameter. With two parameters :
template< typename T1, typename T2 >
and_implementation< T1, T2 > and( T1 && p1, T2 && p2 ){
return and_implementation< T1, T2 >( p1, p2 );
}
In the and_implementation
object I want to store a reference to each parameter that is an lvalue, and a copy of each parameter that is an rvalue. I don't want to use the heap.
The goal is that when I write
auto p1 = ....
auto p2 = ....
auto p3 = and( p1, p3 );
the p3
object contains only references to p1
and p2
, but when I write something like
auto p1 = ....
auto p2 = ....
auto p3 = ....
auto p4 = and( p1, and( p2, p3 ));
the p4
object contains a referencde to p1
, but a copy of and(p2, p3)
.
Is there a way to do this?
What I came up with (the factory is called invert and has only one parameter) is
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 );
}
This works for
autp pin4 = lpc_gpio< 4 >{};
auto led = invert( pin4 );
but for
autp pin4 = lpc_gpio< 4 >{};
auto led = invert( invert( pin4 ));
I get (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>&>'
Upvotes: 1
Views: 258
Reputation: 275385
Just because it is slick:
template<template<class...>class Z, class...Ts>
Z<Ts...> make( Ts&&... ts ) {
return {std::forward<Ts>(ts)...};
}
is a function that can be called like: make<invert_impl>( pin )
, and it deduces the type arguments for invert_impl
. Now, the downside is that it has a bad name. So we can use a function object:
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)...};
}
};
and now we can do:
static make<invert_impl> invert;
and invert(blah)
does the right thing (tm), as does
static make<and_implementation> _and_;
without having to rewrite the glue code. (Note: a variable or function named and
is actually illegal, because under C++ and
is an alias for &&
-- so I called mine _and_
).
Now this gets more fun when we add in named operators (bwahaha).
First the dozen-line library:
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) );
}
}
then what I think is an implementation:
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));
}
};
which gives us
auto r = p1 *_and_* p2;
as an alternative to
auto r = _and_(p1, p2);
which is just fun. (assuming I dotted all the is and crossed all the ts above)
Upvotes: 1
Reputation:
You're over-thinking things. Your constructor doesn't need to be a template, since in every concrete template instantiation, you already know the exact type your constructor should accept: it should accept a T
.
template <typename T>
struct invert_impl : public gpio {
T pin;
invert_impl(T p) : pin(p) {}
};
The reason your template constructor is failing is because it also gets selected as a copy- or move-constructor (if it's a better match than the implicitly generated copy- and move constructors), which cannot work. The copy- and move constructor take const invert_impl &
and invert_impl &&
, which cannot be used to initialise pin
.
Note: the initialisation of pin
from p
may be making an unnecessary copy here. std::forward
can avoid that, even though this isn't exactly what it's originally intended for.
invert_impl(T p) : pin(std::forward<T>(p)) {}
@Yakk rightly points out that there are still some unnecessary operations even then, and they can be avoided by making the constructor take T&&
instead, and forwarding from invert
, like so:
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));
}
Upvotes: 1