1

假设我有 5 节课,AE。

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

实现这一点的最干净的方法是什么?

4

5 回答 5

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
}

活生生的例子

于 2013-10-12T10:34:20.960 回答
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 种类型进行专业化并使用 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;                                                                      
  }  

要创建更通用的方法,您必须创建一个编译时组合元函数

于 2013-10-12T01:05:43.973 回答
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
}
于 2013-10-12T04:00:28.470 回答
0

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

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

于 2013-10-12T13:33:30.867 回答
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{} };
}

现场演示

于 2013-10-15T08:01:33.920 回答