Reputation: 3348
I'm trying to implementing some code where I want to call certain templated methods based on type traits. Unfortunately, the methods are instantiated with types (that are never called with said types) that are incompatible with the function, which causes compilation errors. Ideally I could use the traits to prevent said uncalled methods from being instantiated but that's not the case, so I'm looking for a workaround or design pattern I can use.
I have a few simple POD structs. Each type has different members (their existence is indicated by corresponding traits)
struct ThingA
{
int a;
};
struct ThingAB
{
int a;
int b;
};
struct ThingABC
{
int a;
int b;
int c;
};
template <typename Thing>
struct ThingTraits
{
static const bool has_a=false;
static const bool has_b=false;
static const bool has_c=false;
};
Here's the template function; ThingA, ThingAB and ThingABC can be passed to it as either the Src or the Dst type
template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
constexpr bool c_a = ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a;
constexpr bool c_b = ThingTraits<Src>::has_b && ThingTraits<Dst>::has_b;
constexpr bool c_c = ThingTraits<Src>::has_c && ThingTraits<Dst>::has_c;
c_a ? copy_a(src,dst) : do_nothing();
c_b ? copy_b(src,dst) : do_nothing();
c_c ? copy_c(src,dst) : do_nothing();
}
The copy_a/b/c methods just copy the corresponding member:
template<typename Src, typename Dst>
void copy_a(Src const &src, Dst &dst)
{
dst.a = src.a;
}
template<typename Src, typename Dst>
void copy_b(Src const &src, Dst &dst)
{
dst.b = src.b;
}
template<typename Src, typename Dst>
void copy_c(Src const &src, Dst &dst)
{
dst.c = src.c;
}
When trying to compile this, I get errors because copy_a/b/c are instantiated for types where the required members don't exist (ie copy_b).
How can I implement something functionally equivalent since this way doesn't really work? I need ThingA,AB,ABC to stay as simple PODs with no additional members (size req is strict) and I don't want to determine which copy operations to call during run time.
Upvotes: 3
Views: 132
Reputation: 2433
Although a bit longer than the other answers, here's an example of using SFINAE, specifically the "has some member" trick, to accomplish what you are trying to do:
// Using this as a "type expression container" to force
// the deduction of a type. If said deduction fails,
// the compiler will defer emitting an error message
// until it fails to bind any of the functions below.
//
template<typename nope_t>
struct voidme{using type = void;};
// Using a late return type here allows us to
// reference argument symbols.
//
// Note that the expression within the decltype()
// does not require knowledge of any concrete type.
//
template<typename source_t, typename target_t>
auto copy_a(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.a = src.a)>::type
{
dst.a = src.a;
}
// A "catch-all" copy_a. If type deduction fails in
// the above version, the compiler will attempt to
// bind this version, which will succeed.
//
// This takes advantage of the "Not An Error" part of SFINAE.
//
void copy_a(...)
{
}
template<typename source_t, typename target_t>
auto copy_b(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.b = src.b)>::type
{
dst.b = src.b;
}
void copy_b(...)
{
}
template<typename source_t, typename target_t>
auto copy_c(const source_t &src, target_t &dst)
-> typename voidme<decltype(dst.c = src.c)>::type
{
dst.c = src.c;
}
void copy_c(...)
{
}
template<typename source_t, typename target_t>
void assign(const source_t &src, target_t &dst)
{
copy_a(src, dst);
copy_b(src, dst);
copy_c(src, dst);
}
Elsewhere...
struct ThingWithA
{
int a;
};
struct ThingWithB
{
int b;
};
struct ThingWithAandB
{
int a;
int b;
};
void DoTheThings()
{
ThingWithA srcA, dstA;
ThingWithB srcB, dstB;
ThingWithAandB srcAB, dstAB;
// assign() wherein "copy_b" and "copy_c" do nothing.
assign(srcA, dstA);
// assign() wherein "copy_a" and "copy_c" do nothing.
assign(srcB, dstB);
// assign() wherein only "copy_c" does nothing.
assign(srcAB, dstAB);
}
Note that this approach works for any type, and does not require using traits.
Unless there are other constraints to enforce on the types involved, it is my opinion that parametrized traits you've described are maintenance hazards (they would need to be specialized for every type that needs to be used with assign
, exposing their definitions to this detail).
Upvotes: 2
Reputation: 171117
You can simply use the delegate-to-class trick & (partial) specialisation:
template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
constexpr bool c_a = ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a;
constexpr bool c_b = ThingTraits<Src>::has_b && ThingTraits<Dst>::has_b;
constexpr bool c_c = ThingTraits<Src>::has_c && ThingTraits<Dst>::has_c;
copy_a<c_a>::call(src, dst);
copy_b<c_b>::call(src, dst);
copy_c<c_c>::call(src, dst);
}
template <bool HasA>
struct copy_a;
template <>
struct copy_a<true>
{
template <class Src, class Dst>
static void call(Src const &src, Dst &dst)
{ src.a = dst.a; }
};
template <>
struct copy_a<false>
{
template <class Src, class Dst>
static void call(Src const &, Dst &)
{}
};
copy_b
and copy_c
are left as an excercise for the reader :-)
Upvotes: 3
Reputation: 217145
You may have something like:
template<bool hasA>
struct copy_a_caller
{
template <typename Src, typename Dst>
void operator () (const Src& src, Dst& dst) const
{
dst.a = src.a;
}
};
template<>
struct copy_a_caller<false>
{
template <typename Src, typename Dst>
void operator () (const Src&, Dst&) const {}
};
template<typename Src, typename Dst>
void copy_a(Src const &src, Dst &dst)
{
copy_a_caller<ThingTraits<Src>::has_a && ThingTraits<Dst>::has_a>()(src, dst);
}
// similar thing for b and c
And then
template<typename Src, typename Dst>
void assign(Src const &src, Dst &dst)
{
copy_a(src, dst);
copy_b(src, dst);
copy_c(src, dst);
}
Upvotes: 4