Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Specialize template function on presence or absence of POD structure member in argument type

Given POD structures of the general form

struct case_0   { const char *foo;                       };
struct case_1i  { const char *foo; int v0;               };
struct case_1d  { const char *foo; double v0;            };
struct case_2ii { const char *foo; int v0;    int v1;    };
struct case_2id { const char *foo; int v0;    double v1; };
// etc

is it possible to dispatch to (template) members of a function overload set based on the presence or absence of the v0, v1, etc data members -- ideally, without any dependence on the specific type of these members -- and if so, how? Concretely, given

void
process(const case_0& c)
{
   do_stuff_with(c.foo);
}

template <typename case_1> void   
process(const case_1& c)
{
   do_stuff_with(c.foo, c.v0);
}

template <typename case_2> void
process(const case_2& c)
{
   do_stuff_with(c.foo, c.v0, c.v1);
}

I would like each overload to be selected for all case_* structures that have all the v-members that are used within its body, and -- equally important -- don't have any v-members that are not used within its body.

This program must be 100% self-contained, so no Boost, please. C++11 features are okay.

like image 709
zwol Avatar asked Dec 07 '25 21:12

zwol


2 Answers

You need to write a set of traits such as has_v0 and has_v1 (which I'm sure has been demonstrated many times on SO) then constrain your overloads using them:

template <typename case_0,
  typename = typename std::enable_if<!has_v0<case_0>::value>::type,
  typename = typename std::enable_if<!has_v1<case_0>::value>::type
>
void
process(const case_0& c)
{
   do_stuff_with(c.foo);
}

template <typename case_1,
  typename = typename std::enable_if<has_v0<case_1>::value>::type,
  typename = typename std::enable_if<!has_v1<case_1>::value>::type
>
void   
process(const case_1& c)
{
   do_stuff_with(c.foo, c.v0);
}

template <typename case_2,
  typename = typename std::enable_if<has_v0<case_2>::value>::type,
  typename = typename std::enable_if<has_v1<case_2>::value>::type
>
void
process(const case_2& c)
{
   do_stuff_with(c.foo, c.v0, c.v1);
}

You can simplify the constraints with something like

template<typename Cond>
  using Require = typename std::enable_if<Cond::value>::type;

e.g.

template <typename case_2,
  typename = Require<has_v0<case_2>>,
  typename = Require<has_v1<case_2>>
>
void
process(const case_2& c)
{
   do_stuff_with(c.foo, c.v0, c.v1);
}
like image 101
Jonathan Wakely Avatar answered Dec 10 '25 11:12

Jonathan Wakely


One solution is provided by @Jonathan Wakely which employs the use of has_XXX meta-functions.

Here is another solution, but it requires you to change your full-fledge structs definitions to mere typedefs of std::tuple<>.

full-fledge structs:

struct case_0   { const char *foo;                       };
struct case_1i  { const char *foo; int v0;               };
struct case_1d  { const char *foo; double v0;            };
struct case_2ii { const char *foo; int v0;    int v1;    };
struct case_2id { const char *foo; int v0;    double v1; };

are replaced with typedefs structs as follows:

typedef std::tuple<const char*>            case_0;
typedef std::tuple<const char*,int>        case_1i;
typedef std::tuple<const char*,double>     case_1d;
typedef std::tuple<const char*,int,int>    case_2ii;
typedef std::tuple<const char*,int,double> case_2id;

template<typename...Args>
auto foo(std::tuple<Args...> & tpl) -> decltype(std::get<0>(tpl))&
{
     return std::get<0>(tpl);
}

template<typename...Args>
auto v0(std::tuple<Args...> & tpl) -> decltype(std::get<1>(tpl))&
{
     return std::get<1>(tpl);
}

template<typename...Args>
auto v1(std::tuple<Args...> & tpl) -> decltype(std::get<2>(tpl))&
{
     return std::get<2>(tpl);
}

and the usage

case_1i obj; //full-fledge struct
obj.foo = "hello";
obj.v0 = 100;

is replaced with

case_1i obj; //typedef struct
foo(obj) = "hello";
v0(obj) = 100;

Once you accept this design change, the solution to your original problem becomes pretty much straight-forward as follows:

template<size_t...>
struct seq{};

template<size_t M, size_t ...N>
struct genseq  : genseq<M-1,M-1, N...> {};

template<size_t ...N>
struct genseq<0,N...>
{
    typedef seq<N...> type;
};

template <typename ...Args, size_t ...N> 
void call_do_stuff_with(std::tuple<Args...> & tpl, seq<N...>)
{
    do_stuff_with(std::get<N>(tpl)...);
}

template <typename ...Args> 
void process(std::tuple<Args...> & tpl)
{
   const size_t N = sizeof ...(Args);
   call_do_stuff_with(tpl, typename genseq<N>::type());
}

Do let me know if that is acceptable. If that is not acceptable, I will delete my answer (if you feel so).

Live demo!

like image 24
Nawaz Avatar answered Dec 10 '25 11:12

Nawaz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!