2013-10-12 26 views
1

說我有5個類,A-E。以任意順序傳遞參數的構造器排列

我想創建一個類的小工具,它可以從0-5參數構建,這些參數被約束爲類型A,B,C,D或E的常量引用,並且沒有任何重複。

什麼是最簡潔的方式來實現呢?

+0

這意味着你可能有'5!+4!+3!+2!+1!+ 0!= 154'構造。你真的想生成154個構造函數嗎?我想你想要的是以任意順序命名參數和可選的。例如'Gadget g(A = p,E = q,B = r);',或者當然,它是無效的C++。你想要類似這樣的東西嗎? – Ali

回答

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

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元函數

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

+0

這是非常接近,但創建一個額外的副本相比,一個正常的位置參數構造函數。 [鏈接](http://ideone.com/ioWDzQ) –

+0

@MichaelMarcin我更新了代碼以減少臨時對象。將新的'dftl :: value'集成到您的測試中,看看它是否有幫助。 (當然,必須構造一組虛擬/默認值,因此如果第一個實例化包含它們,請嘗試第二個實例並再次計數)。 –

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

遠非完美,但我個人覺得它更容易閱讀和理解比使用模板元編程的解決方案。