用于以任意顺序传递参数的构造函数排列

时间:2013-10-12 00:53:34

标签: c++ c++11 constructor permutation

说我有5个班级,A-E。

我想创建一个可以从0-5参数构造的类小工具,这些参数被约束为任何顺序的类型A,B,C,D或E的const引用,并且没有重复。

实施此最简洁的方法是什么?

5 个答案:

答案 0 :(得分:1)

以下解决了您的问题:

#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
}

Live example

答案 1 :(得分:0)

轻松使用可变参数模板和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的专业化类型并使用static_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;                                                                      
  }  

要创建更通用的方法,您需要创建编译时combination元函数

答案 2 :(得分: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
}

答案 3 :(得分:0)

如果您愿意对语法做出妥协,可以使用Builder pattern进行操作。用法如下:

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

远非完美,但我个人觉得比使用模板元编程的解决方案更容易阅读和理解。

答案 4 :(得分:0)

这是一个有效的解决方案,仍然不确定最佳解决方案。

#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{} };
}

Live Demo