Related
In the following code, what is the difference between the following two template lines.
> 1. template<class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
> 2. template<class T, typename = std::enable_if_t<std::is_integral<T>::value>>
Both the above lines are working fine, I just wanted to know the advantages/disadvantage in using one over the other.
#include <type_traits>
#include <iostream>
template<class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
//template<class T, typename = std::enable_if_t<std::is_integral<T>::value>>
int onlyOnInt(T a, T b)
{
return a+b;
}
int main()
{
onlyOnInt(1, 2);
}
They are both working fine, if you write a single function.
But when you want two alternative functions, this way
template <typename T, typename = std::enable_if_t<true == std::is_integral_v<T>>>
void foo (T const &)
{ std::cout << "is integral" << std::endl; }
template <typename T, typename = std::enable_if_t<false == std::is_integral_v<T>>>
void foo (T const &)
{ std::cout << "isn\'t integral" << std::endl; }
you get a compilation error where this way
template <typename T, std::enable_if_t<true == std::is_integral_v<T>, int> = 0>
void foo (T const &)
{ std::cout << "is integral" << std::endl; }
template <typename T, std::enable_if_t<false == std::is_integral_v<T>, int> = 0>
void foo (T const &)
{ std::cout << "isn\'t integral" << std::endl; }
works.
The reason?
Consider you're playing with SFINAE, that is Substitution Failure Is Not An Error.
The point is Substitution.
The first way, when you call
foo(0)
the substitution bring to
template <typename T, typename = void>
void foo (T const &)
{ std::cout << "is integral" << std::endl; }
template <typename T, typename>
void foo (T const &)
{ std::cout << "isn\'t integral" << std::endl; }
that is... you have two functions with the same signatures (a default template argument doesn't change the signature of a function) and a collision calling it.
In the second way you have only
template <typename T, int = 0>
void foo (T const &)
{ std::cout << "is integral" << std::endl; }
because the substitution failure in the second function make the function unusable and it's discarded. So you have only a function available and no collision.
callable should be any function pointer, std::function or lambda. I want to obtain their argument list and use them as parameter pack:
template <typename callable_T>
class callback2_t
{
public:
using callable_t = callable_T;
using ret_T = some_magic<callable_T>::ret_t;
using data_T = the_first_type<argTs>;
...
static ret_T _callback(some_magic<callable_T>::argTs... args);
};
The purpose is to simplify follow templates to make it work for all kind of callable without creating alias:
// other library only accept function pointer as callback, I want to wrap it to remove the callback when data go out of scope.
template <typename callable_T, typename ret_T, typename data_T, typename ...arg_Ts>
class callback_t
{
using callable_t = callable_T;
public:
callback_t(const char* signal, callable_t callable, data_T data)
: f_{std::move(callable)}, data_{std::move(data)}
{
std::cout << signal << " " << typeid(callable).name() << std::endl;
//register_callback(signal, _callback, this);
}
~callback_t()
{
//unregister_callback(signal);
}
void test_callback(arg_Ts... args)
{
_callback(args..., this);
}
private:
callable_t f_;
data_T data_;
static ret_T _callback(arg_Ts... args, callback_t * self)
{
return self->f_(&self->data_, args...);
}
};
// I donot want convert function pointer to std::function
// if possible. std::function is a heavy class.
template <typename ret_T, typename data_T, typename ...arg_Ts>
using fp_callback_t = callback_t<ret_T(*)(void *, arg_Ts...), ret_T, data_T, arg_Ts...>;
template <typename ret_T, typename data_T, typename ...arg_Ts>
using func_callback_t = callback_t<std::function<ret_T(void *, arg_Ts...)>, ret_T, data_T, arg_Ts...>;
We can use the template like this:
struct A{float x;};
struct B{int x;};
struct C{uint x;};
int func1(void * data, A* a)
{
auto c = reinterpret_cast<C*>(data);
std::cout<< a->x << ", " << c->x << std::endl;
return a->x + c->x;
}
void func2(void *data, B* b, C* c)
{
auto a = reinterpret_cast<A*>(data);
std::cout << b->x << ", " << c->x << ", " << a->x << std::endl;
}
int main()
{
A a1{-10.5f};
B b1 {5};
C c1{300};
auto callback1 = fp_callback_t<int, C, A*>("signal1", &func1, c1);
callback1.test_callback(&a1);
auto callback2 = fp_callback_t<void, A, B*, C*>("signal2", &func2, a1);
callback2.test_callback(&b1, &c1);
std::function<int(void*, A*)> fc1 = [=](void* data, A* a){
auto c = reinterpret_cast<C*>(data);
std::cout<< a->x << ", " << c->x << ", " << a1.x << std::endl;
return (int)a1.x;
};
std::function<void(void*, B*, C*)> fc2 = [=](void* data, B* b, C* c){
auto a = reinterpret_cast<A*>(data);
std::cout << b->x << ", " << c->x << ", " << a->x << ", " << c1.x << std::endl;
};
auto callback3 = func_callback_t<int, C, A*>("signal3", fc1, c1);
callback3.test_callback(&a1);
auto callback4 = func_callback_t<void, A, B*, C*>("signal4", fc2, a1);
callback4.test_callback(&b1, &c1);
return 0;
}
The out put is:
signal1 PFiPvP1AE
-10.5, 300
signal2 PFvPvP1BP1CE
5, 300, -10.5
signal3 NSt3__18functionIFiPvP1AEEE
-10.5, 300, -10.5
signal4 NSt3__18functionIFvPvP1BP1CEEE
5, 300, -10.5, 300
The deduction should work without specialize template parameters explicitly; I want to avoid alias; It should work with function pointer, std::function and lambda; the callable_t should be as it is given instead of casting all of them to std::function. like following:
auto callback1 = callback2_t("signal1", &func1, c1);
callback1.test_callback(&a1);
auto callback2 = callback2_t("signal2", &func2, a1);
callback2.test_callback(&b1, &c1);
std::function<int(void*, A*)> fc1 = [=](void* data, A* a){
auto c = reinterpret_cast<C*>(data);
std::cout<< a->x << ", " << c->x << ", " << a1.x << std::endl;
return (int)a1.x;
};
auto callback3 = callback2_t("signal3", fc1, c1);
callback3.test_callback(&a1);
auto lambda1 = [=](void* data, B* b, C* c){
auto a = reinterpret_cast<A*>(data);
std::cout << b->x << ", " << c->x << ", " << a->x << ", " << c1.x << std::endl;
};
auto callback4 = callback2_t("signal4", lambda1, a1);
callback4.test_callback(&b1, &c1);
You don't need to deduce the parameters at all. Just let the template soak them up and forward them to the callable.
template <typename callable_T, typename data_T>
class callback_t
{
using callable_t = callable_T;
public:
callback_t(const char* signal, callable_t callable, data_T data)
: f_{std::move(callable)}, data_{std::move(data)}
{
}
template<typename...arg_Ts>
auto test_callback(arg_Ts... args)
{
return _callback(this, args...);
}
private:
callable_t f_;
data_T data_;
template<typename...arg_Ts>
static auto _callback(callback_t * self, arg_Ts... args)
{
return self->f_(&self->data_, args...);
}
};
Works great even if operator() is overloaded:
int test()
{
callback_t cc("test", [](auto x, auto y){ return *x + y;}, 42);
return cc.test_callback(9); // returns 42 + 9 = 51
}
That was an interesting quiz =) The trick was to pass pointer to lambda::operator() and let type deduction work:
template <typename Ret, typename ... Args>
struct Callback {
Callback() {
std::cout << "Created " << typeid(*this).name() << "\n";
}
};
template <typename Ret, typename ... Args>
Callback<Ret, Args...> create_callback(std::function<Ret (Args...)> const &fn) {
std::cout << "Function!\n";
return Callback<Ret, Args...>();
}
template <typename Ret, typename ... Args>
Callback<Ret, Args...> create_callback(Ret (*fn)(Args...)) {
std::cout << "Function pointer!\n";
return Callback<Ret, Args...>();
}
template <typename Lambda, typename Ret, typename ... Args>
Callback<Ret, Args...> create_callback_lambda(Lambda const &, Ret (Lambda::*)(Args...) const) {
std::cout << "Lambda!\n";
return Callback<Ret, Args...>();
}
template <typename Callable>
auto create_callback(Callable const &c) {
return create_callback_lambda(c, &Callable::operator());
}
I am trying to understand the usage of enable_if but I do have few difficulties in the same. Here I have written a test code that doesn't seem to work as intended.
#include <iostream>
template <typename T>
class Base{
public:
template <typename U>
U Compute(U a, U b);
};
using AddOperation = Base<int>;
template<>
template<typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type
AddOperation::Compute(U a, U b){
return a + b;
}
int main(){
Base<int> b;
std::cout << b.Compute<int>(10, 2) << std::endl;
std::cout << b.Compute<bool>(true, false) << std::endl;
return 0;
}
Intention: Don't want to enable Compute for bool type
but in the code above, it is working. How do I make sure that the Compute function for bool is not specialized by compiler?
EDIT1
Eventual goal is to enable Compute for U=bool for T=T1 and disable Compute for U=bool for T=T2. Here is another example code by which I am trying to achieve the same
#include <iostream>
enum class OpType{
INT,
BITWISE,
};
template <OpType T>
class Base{
public:
template <typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type
Compute(U a, U b);
};
using AddOperation = Base<OpType::INT>;
template<>
template<typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type
AddOperation::Compute(U a, U b){
std::cout << a << "," << b << std::endl;
return a + b;
}
using AndOperation = Base<OpType::BITWISE>;
template<>
template<typename U>
typename std::enable_if<std::is_same<U, bool>::value, U>::type
AndOperation::Compute(U a, U b){
return a & b;
}
int main(){
AddOperation b;
AndOperation a;
std::cout << b.Compute<int>(10, 2) << std::endl;
std::cout << a.Compute<bool>(true, true) << std::endl;
return 0;
}
You should use enable_if in the declaration too, as the definition did.
template <typename T>
class Base{
public:
template <typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type Compute(U a, U b);
};
LIVE
In fact, clang rejects your current code as I expected, because the declaration and defintion don't match.
error: out-of-line definition of 'Compute' does not match any declaration in 'Base'
EDIT (for your added question)
You can
template <OpType T>
class Base{
public:
template <typename U, OpType X = T>
typename std::enable_if<
(X == OpType::INT && !std::is_same<U, bool>::value)
||
(X == OpType::BITWISE && std::is_same<U, bool>::value), U
>::type
Compute(U a, U b);
};
using AddOperation = Base<OpType::INT>;
template<>
template<typename U, OpType X>
typename std::enable_if<
(X == OpType::INT && !std::is_same<U, bool>::value)
||
(X == OpType::BITWISE && std::is_same<U, bool>::value), U
>::type
AddOperation::Compute(U a, U b){
std::cout << a << "," << b << std::endl;
return a + b;
}
using AndOperation = Base<OpType::BITWISE>;
template<>
template<typename U, OpType X>
typename std::enable_if<
(X == OpType::INT && !std::is_same<U, bool>::value)
||
(X == OpType::BITWISE && std::is_same<U, bool>::value), U
>::type
AndOperation::Compute(U a, U b){
return a & b;
}
then
std::cout << b.Compute<int>(10, 2) << std::endl; // fine
std::cout << a.Compute<bool>(true, true) << std::endl; // fine
std::cout << b.Compute<bool>(true, true) << std::endl; // error, no matching function
std::cout << a.Compute<int>(10, 2) << std::endl; // error, no matching function
LIVE
Another approach is class template specialization, to seperate the implementation of OpType::INT and OpType::BITWISE.
It is not really answer of your question but there is a better way to forbid bool argument for Compute( T , T ) function for sake of readability.
template <typename T>
class Base{
public:
template <typename U>
U Compute(U a, U b);
bool Compute(bool , bool) = delete;
};
If the intention is to disable it only for bool types, have the method implemented for the primary class template Base<T>. You need enable_if in the declaration of Compute() too.
#include <iostream>
template <typename T>
class Base{
public:
template <typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type Compute(U a, U b);
};
template<typename T>
template<typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type
Base<T>::Compute(U a, U b){
return a + b;
}
int main(){
Base<int> b;
std::cout << b.Compute<int>(10, 2) << std::endl;
//std::cout << b.Compute<bool>(true, false) << std::endl; --> compile error
return 0;
}
EDIT1
Do class template specialization for Base class to achieve the results. Create a full specialization for OpType::BITWISE and implement your Compute function.
#include <iostream>
enum class OpType{
INT,
BITWISE,
};
template <OpType T>
class Base{
public:
template <typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type Compute(U a, U b);
};
template<OpType T>
template<typename U>
typename std::enable_if<!std::is_same<U, bool>::value, U>::type
Base<T>::Compute(U a, U b){
return a + b;
}
template <>
struct Base<OpType::BITWISE> {
template <typename U>
U Compute(U a, U b) { return a & b;}
};
using AddOperation = Base<OpType::INT>;
using AndOperation = Base<OpType::BITWISE>;
int main(){
AddOperation b;
AndOperation a;
std::cout << b.Compute<int>(10, 2) << std::endl;
std::cout << a.Compute<bool>(true, true) << std::endl;
return 0;
}
Consider the following code:
#include <utility>
#include <iostream>
struct S {
template<typename T, typename... A>
auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f(...) {
std::cout << "has not f(int)" << std::endl;
}
};
struct T { void f(int) { } };
struct U { };
int main() {
S s;
s.f<T>(42); // -> has f(int)
s.f<U>(42); // -> has not f(int)
// oops
s.f<T>(); // -> has not f(int)
}
As shown in the example the third call to f works just fine, even if the number of arguments is wrong, for it's not wrong at all for the fallback function.
Is there a way to force the number of arguments when an ellipsis is involved that way?
I mean, can I check at compile time that the size of the arguments list is exactly 1, no matter if the main function or the fallback is chosen?
Good solutions are also the ones that only involves the first template function and result in hard-errors instead of soft-errors because of the size of the parameter pack.
Of course, it can be solved with several techniques without using variadic arguments. As an example: int/char dispatching on internal template methods; explicitly specify the arguments list; whatever...
The question is not about alternative approaches to do that, I already know them.
It's just to know if I'm missing something basic here or it's not possible and that's all.
If I understand correctly your issue, you may add a layer:
struct S {
private:
template<typename T, typename... A>
auto f_impl(A&&... args)
-> decltype(std::declval<T>().f(std::forward<A>(args)...), void()) {
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f_impl(...) {
std::cout << "has not f(int)" << std::endl;
}
public:
template<typename T, typename A>
auto f(A&& args) { return f_impl<T>(std::forward<A>(arg)); }
};
With traits, you may do
template <typename T, typename ... Ts>
using f_t = decltype(std::declval<T>().f(std::declval<Ts>()...));
template <typename T, typename ... Ts>
using has_f = is_detected<f_t, T, Ts...>;
struct S {
template<typename T, typename... A>
std::enable_if_t<has_f<T, A&&...>::value && sizeof...(A) == 1> f(A&&... args)
{
std::cout << "has f(int)" << std::endl;
}
template<typename T, typename... A>
std::enable_if_t<!has_f<T, A&&...>::value && sizeof...(A) == 1> f(A&&... args) {
std::cout << "has not f(int)" << std::endl;
}
};
Demo
You can use a function (assert) that gets pointer to a function to deduce size of paramemters :
#include <utility>
#include <iostream>
template <typename...Args>
struct size_assert{
template <typename T,typename R,typename... Params>
constexpr static bool assert(R(T::*)(Params...) )
{
static_assert(sizeof...(Args) == sizeof...(Params),"Incorrect size of arguments!");
return true;
}
};
struct S {
template<typename T, typename... A, bool = size_assert<A...>::assert(&T::f)>
auto f(A&&... args) -> decltype(std::declval<T>().f(std::forward<A>(args)...), void())
{
std::cout << "has f(int)" << std::endl;
}
template<typename>
void f(...) {
std::cout << "has not f(int)" << std::endl;
}
};
struct T { void f(int) { } };
struct U { };
int main() {
// std::cout <<fc(&f);
S s;
s.f<T>(42); // -> has f(int)
s.f<U>(42); // -> has not f(int)
// oops
s.f<T>(); // -> has not f(int)
}
Say I have 5 classes, A-E.
I want to create a class Gadget that can be constructed from 0-5 parameters which are constrained to be a const reference to types A,B,C,D, or E in any order and without duplicates.
What is the cleanest way to implement this?
The following solves your problem:
#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
Easy just use variadic templates and a 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;
}
Preventing duplicates might be tricky, I still have to think of that one.
Honestly I can't think of any un-painful way to ensure that all the types are different but the solution will likely involve std::is_same one definite way to make it work would be to have specializations for 0 - 5 types and use a static_assert to check all the combinations in each specialization, this will definitely be a pain though.
EDIT: well this was fun
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;
}
To create a more general approach what you have to do is create a compile time combination meta function
The question asks for a Gaget class that can be constructed with [0-5] number of parameters constrained to 5 different types without duplication, and with any order. With the help of templates it's doable; below is an example for two parameters, and it's easily extensible to 5 parameters.
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
}
If you are willing to make compromises on the syntax, you can do it with the Builder pattern. The usage would look like this:
Gadget g = Gadget::builder(c)(a)(b)();
Yes, this syntax is not very nice and perhaps a bit obscure but it is a reasonable compromise. The good news is that you avoid the combinatorial explosion: This solutions scales linearly with the number of arguments. One downside is that duplicate arguments are detected only at runtime.
Sample code for 3 types (may contain errors):
#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;
}
Far from perfect but I personally find it easier to read and understand than the solutions using template metaprogramming.
Here is a working solution, still not sure of the optimal solution.
#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