【问题标题】:Constructor permutations for passing parameters in arbitrary order以任意顺序传递参数的构造函数排列
【发布时间】:2013-10-12 00:53:34
【问题描述】:

假设我有 5 节课,A-E。

我想创建一个类 Gadget,它可以由 0-5 个参数构成,这些参数被限制为以任意顺序对 A、B、C、D 或 E 类型的 const 引用,并且没有重复。

什么是最简洁的实现方式?

【问题讨论】:

  • 这意味着您可能拥有5!+4!+3!+2!+1!+0!=154 构造函数。你真的要生成 154 个构造函数吗?我猜你想要的是任意顺序和可选的命名参数。例如Gadget g(A=p, E=q, B=r);,或者当然,它不是有效的 C++。你想要类似的东西吗?

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


【解决方案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

【讨论】:

  • 这非常接近,但与普通的位置参数构造函数相比,它创建了一个额外的副本。 link
  • @MichaelMarcin 我更新了代码以减少临时性。将新的dftl&lt;Ts...&gt;::value 集成到您的测试中,看看它是否有帮助。 (当然,必须构造一组虚拟/默认值,因此如果第一个实例包含它们,请尝试第二个并再次计数)。
【解决方案2】:

只需使用可变参数模板和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 元函数

【讨论】:

    【解决方案3】:

    该问题要求构建一个 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
    }
    

    【讨论】:

      【解决方案4】:

      如果您愿意在语法上做出妥协,您可以使用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;
      }
      

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

      【讨论】:

        【解决方案5】:

        这是一个可行的解决方案,但仍不确定最佳解决方案。

        #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

        【讨论】:

          猜你喜欢
          • 2014-03-26
          • 2011-04-21
          • 2010-12-24
          • 1970-01-01
          • 2015-01-13
          • 1970-01-01
          • 2023-03-15
          • 1970-01-01
          • 2011-11-30
          相关资源
          最近更新 更多