假设我有 5 节课,AE。
我想创建一个可以从 0-5 个参数构造的类 Gadget,这些参数被限制为以任何顺序对 A、B、C、D 或 E 类型的 const 引用,并且没有重复项。
实现这一点的最干净的方法是什么?
假设我有 5 节课,AE。
我想创建一个可以从 0-5 个参数构造的类 Gadget,这些参数被限制为以任何顺序对 A、B、C、D 或 E 类型的 const 引用,并且没有重复项。
实现这一点的最干净的方法是什么?
以下解决您的问题:
#include <type_traits>
#include <tuple>
// find the index of a type in a list of types,
// return sizeof...(Ts) if T is not found
template< typename T, typename... Ts >
struct index_by_type : std::integral_constant< std::size_t, 0 > {};
template< typename T, typename... Ts >
struct index_by_type< T, T, Ts... > : std::integral_constant< std::size_t, 0 >
{
static_assert( index_by_type< T, Ts... >::value == sizeof...( Ts ), "duplicate type detected" );
};
template< typename T, typename U, typename... Ts >
struct index_by_type< T, U, Ts... > : std::integral_constant< std::size_t, index_by_type< T, Ts... >::value + 1 > {};
// get the element from either "us" if possible...
template< std::size_t I, std::size_t J, typename T, typename... Us, typename... Ts >
auto get_by_index( const std::tuple< Us... >&, const std::tuple< Ts... >& ts )
-> typename std::enable_if< I == sizeof...( Us ), const T& >::type
{
return std::get< J >( ts );
}
// ...get the element from "ts" otherwise
template< std::size_t I, std::size_t J, typename T, typename... Us, typename... Ts >
auto get_by_index( const std::tuple< Us... >& us, const std::tuple< Ts... >& )
-> typename std::enable_if< I != sizeof...( Us ), const T& >::type
{
return std::get< I >( us );
}
// helper to validate that all Us are in Ts...
template< bool > struct invalide_type;
template<> struct invalide_type< true > : std::true_type {};
template< std::size_t... > void validate_types() {}
template< typename T >
struct dflt
{
static const T value;
};
template< typename T >
const T dflt< T >::value;
// reorder parameters
template< typename... Ts, typename... Us >
std::tuple< const Ts&... > ordered_tie( const Us&... us )
{
auto t1 = std::tie( us... );
auto t2 = std::tie( dflt< Ts >::value... );
validate_types< invalide_type< index_by_type< const Us&, const Ts&... >::value != sizeof...( Ts ) >::value... >();
return std::tie( get_by_index< index_by_type< const Ts&, const Us&... >::value,
index_by_type< const Ts&, const Ts&... >::value, Ts >( t1, t2 )... );
}
struct A {};
struct B {};
struct C {};
struct Gadget
{
A a;
B b;
C c;
explicit Gadget( const std::tuple< const A&, const B&, const C& >& t )
: a( std::get<0>(t) ),
b( std::get<1>(t) ),
c( std::get<2>(t) )
{}
template< typename... Ts >
Gadget( const Ts&... ts ) : Gadget( ordered_tie< A, B, C >( ts... ) ) {}
};
int main()
{
A a;
B b;
C c;
Gadget g1( a, b, c );
Gadget g2( b, c, a );
Gadget g3( a, b ); // uses a default-constructed C
Gadget g4( a, c ); // uses a default-constructed B
Gadget g5( c ); // uses a default-constructed A and B
Gadget g6; // uses a default-constructed A, B and C
// fails to compile:
// Gadget gf1( a, a ); // duplicate type
// Gadget gf2( a, b, 42 ); // invalid type
}
只需使用可变参数模板和static_assert
template <typename ... Types>
struct thing
{
static_assert(sizeof...(Types) <= 5,"Too many objects passed");
};
int main()
{
thing<int,float,double,int,int> a;
return 0;
}
防止重复可能很棘手,我仍然必须考虑那个。
老实说,我想不出任何不痛的方法来确保所有类型都不同,但解决方案可能涉及std::is_same
一种使其工作的明确方法是对 0 - 5 种类型进行专业化并使用 astatic_assert
检查所有每个专业的组合,这肯定会很痛苦。
编辑:这很有趣
template <typename ... Types>
struct thing
{
static_assert(sizeof ... (Types) <= 5,"Too big");
};
template <>
struct thing<> {};
template <typename A>
struct thing<A>{};
template <typename A, typename B>
struct thing<A,B>
{
static_assert(!std::is_same<A,B>::value,"Bad");
};
template <typename A, typename B, typename C>
struct thing<A,B,C>
{
static_assert(!std::is_same<A,B>::value &&
!std::is_same<A,C>::value &&
!std::is_same<C,B>::value,"Bad");
};
template <typename A, typename B, typename C, typename D>
struct thing<A,B,C,D>
{
static_assert(!std::is_same<A,B>::value &&
!std::is_same<A,C>::value &&
!std::is_same<C,B>::value &&
!std::is_same<C,D>::value &&
!std::is_same<B,D>::value &&
!std::is_same<A,D>::value,"Bad");
};
template <typename A, typename B, typename C, typename D, typename E>
struct thing<A,B,C,D,E>
{
static_assert(!std::is_same<A,B>::value &&
!std::is_same<A,C>::value &&
!std::is_same<C,B>::value &&
!std::is_same<C,D>::value &&
!std::is_same<B,D>::value &&
!std::is_same<A,D>::value &&
!std::is_same<A,E>::value &&
!std::is_same<B,E>::value &&
!std::is_same<C,E>::value &&
!std::is_same<D,E>::value,"Bad");
};
int main()
{
thing<> a;
thing<int,float,int> b; //error
thing<int,float,double,size_t,char> c;
thing<int,float,double,size_t,char,long> d; //error
return 0;
}
要创建更通用的方法,您必须创建一个编译时组合元函数
该问题要求一个 Gaget 类,该类可以使用 [0-5] 个限制为 5 种不同类型且不重复且具有任何顺序的参数数量来构造。在模板的帮助下,它是可行的;下面是两个参数的示例,它很容易扩展到 5 个参数。
class A
{
};
class B
{
};
template<typename T> struct is_A
{
enum { value = false };
};
template<> struct is_A<A>
{
enum { value = true };
};
template<typename T> struct is_B
{
enum { value = false };
};
template<> struct is_B<B>
{
enum { value = true };
};
template <bool V> struct bool_to_count
{
enum {value = V ? 1 : 0};
};
class Gaget
{
public:
template <typename T1> Gaget(const T1& t1)
{
static_assert(is_A<T1>::value || is_B<T1>::value, "T1 can only be A or B");
if (is_A<T1>::value)
{
m_a = *reinterpret_cast<const A*>(&t1);
}
if (is_B<T1>::value)
{
m_b = *reinterpret_cast<const B*>(&t1);
}
}
template <typename T1, typename T2> Gaget(const T1& t1, const T2& t2)
{
static_assert(is_A<T1>::value || is_B<T1>::value, "T1 can only be A or B");
static_assert(is_A<T2>::value || is_B<T2>::value, "T2 can only be A or B");
const int countA = bool_to_count<is_A<T1>::value>::value
+ bool_to_count<is_A<T2>::value>::value;
static_assert(countA == 1, "One and only one A is allowed");
const int countB = bool_to_count<is_B<T1>::value>::value
+ bool_to_count<is_B<T2>::value>::value;
static_assert(countA == 1, "One and only one B is allowed");
if(is_A<T1>::value)
{
// it's safe because it's only executed when T1 is A;
// same with all following
m_a = *reinterpret_cast<const A*>(&t1);
}
if(is_B<T1>::value)
{
m_b = *reinterpret_cast<const B*>(&t1);
}
if (is_A<T2>::value)
{
m_a = *reinterpret_cast<const A*>(&t2);
}
if (is_B<T2>::value)
{
m_b = *reinterpret_cast<const B*>(&t2);
}
}
private:
A m_a;
B m_b;
};
void foo(const A& a, const B& b)
{
auto x1 = Gaget(b,a);
auto x2 = Gaget(a,b);
auto x3 = Gaget(a);
auto x4 = Gaget(b);
// auto x5 = Gaget(a,a); // error
// auto x6 = Gaget(b,b); // error
}
如果您愿意在语法上做出妥协,您可以使用Builder 模式。用法如下所示:
Gadget g = Gadget::builder(c)(a)(b)();
是的,这种语法不是很好,可能有点晦涩,但它是一个合理的折衷方案。好消息是您避免了组合爆炸:此解决方案与参数的数量呈线性关系。一个缺点是仅在运行时检测到重复的参数。
3种类型的示例代码(可能包含错误):
#include <iostream>
#include <stdexcept>
struct A { char value = ' '; };
struct B { char value = ' '; };
struct C { char value = ' '; };
struct state { A a; B b; C c; };
class Gadget {
private:
Gadget(state s) : s(s) { };
state s;
public:
class builder {
public:
template <class T>
builder(T t) { reference(t) = t; }
template <class T>
builder& operator()(T t) { return assign(reference(t), t); }
Gadget operator()() { return Gadget(s); }
private:
template <class T>
builder& assign(T& self, T t) {
if (self.value != ' ')
throw std::logic_error("members can be initialized only once");
self = t;
return *this;
}
A& reference(A ) { return s.a; }
B& reference(B ) { return s.b; }
C& reference(C ) { return s.c; }
state s;
};
friend std::ostream& operator<<(std::ostream& out, const Gadget& g) {
out << "A: " << g.s.a.value << std::endl;
out << "B: " << g.s.b.value << std::endl;
return out << "C: " << g.s.c.value << std::endl;
}
};
int main() {
A a; a.value = 'a';
B b; b.value = 'b';
C c; c.value = 'c';
Gadget g = Gadget::builder(c)(a)(b)();
std::cout << "Gadget:\n" << g << std::endl;
}
远非完美,但我个人发现它比使用模板元编程的解决方案更容易阅读和理解。
这是一个可行的解决方案,但仍不确定最佳解决方案。
#include <iostream>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/set.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/placeholders.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/if.hpp>
#include <boost/mpl/has_key.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/fusion/include/for_each.hpp>
struct A{
A() { std::cout << "A default constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
A( const A& ) { std::cout << "A copy constructor" << std::endl; }
A( A&& ) { std::cout << "A move constructor" << std::endl; }
};
struct B{
B() { std::cout << "B default constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
B( const B& ) { std::cout << "B copy constructor" << std::endl; }
B( B&& ) { std::cout << "B move constructor" << std::endl; }
};
struct C{
C() { std::cout << "C default constructor" << std::endl; }
~C() { std::cout << "C destructor" << std::endl; }
C( const C& ) { std::cout << "C copy constructor" << std::endl; }
C( C&& ) { std::cout << "C move constructor" << std::endl; }
};
struct D{
D() { std::cout << "D default constructor" << std::endl; }
~D() { std::cout << "D destructor" << std::endl; }
D( const D& ) { std::cout << "D copy constructor" << std::endl; }
D( D&& ) { std::cout << "D move constructor" << std::endl; }
};
struct E{
E() { std::cout << "E default constructor" << std::endl; }
~E() { std::cout << "E destructor" << std::endl; }
E( const E& ) { std::cout << "E copy constructor" << std::endl; }
E( E&& ) { std::cout << "E move constructor" << std::endl; }
};
class Gadget
{
struct call_setters
{
Gadget& self;
call_setters( Gadget& self_ ) : self( self_ ){}
template< typename T >
void operator()( T& t ) const
{
self.set( t );
}
};
public:
template< typename... Args >
Gadget( const Args&... args )
{
using namespace boost::mpl;
using namespace boost::mpl::placeholders;
typedef vector<A, B, C, D, E> allowed_args;
static_assert(sizeof...(Args) <= size<allowed_args>::value, "Too many arguments");
typedef typename fold< vector<Args...>
, set0<>
, insert<_1, _2>
>::type unique_args;
static_assert(size<unique_args>::value == sizeof...(Args), "Duplicate argument types");
typedef typename fold< allowed_args
, int_<0>
, if_< has_key<unique_args, _2 >, next<_1>, _1 >
>::type allowed_arg_count;
static_assert(allowed_arg_count::value == sizeof...(Args), "One or more argument types are not allowed");
namespace bf = boost::fusion;
bf::for_each( bf::vector<const Args&...>( args... ), call_setters{ *this } );
}
void set( const A& ) { std::cout << "Set A" << std::endl; }
void set( const B& ) { std::cout << "Set B" << std::endl; }
void set( const C& ) { std::cout << "Set C" << std::endl; }
void set( const D& ) { std::cout << "Set D" << std::endl; }
void set( const E& ) { std::cout << "Set E" << std::endl; }
};
int main()
{
Gadget{ A{}, E{}, C{}, D{}, B{} };
}