2

I'm looking to do something like this:

void func(void *data, const int dtype)
{
    typedef typename std::conditional<dtype==0,float,double>::type DataType;

    funcT((DataType *)data);

    return;
}

This will not compile because dtype needs to be known at compile time. I'm trying to avoid using a switch statement, because I have 8 data types I am working with, with many functions such as the one above, being called from Python via ctypes.

Is there a way something like std::conditional can done during run time, making use of the dtype identifier passed in?

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
wolfblade87
  • 173
  • 10
  • Have you tried `constexpr` instead of `const`? Anyway, you have to know the value at compile time. – scrutari Sep 08 '17 at 18:27
  • Type names cannot depend on runtime values. – François Andrieux Sep 08 '17 at 18:28
  • This can just work in constexpr function which are not run-time. – dgrat Sep 08 '17 at 18:30
  • 3
    What is wrong with a `switch`? How does `std::conditional` help with eight types? – Kane Sep 08 '17 at 18:35
  • @dgrat This comment is a bit misleading. Parameters sent to constexpr functions looses their constexpr-ness, and constexpr function work perfectly at runtime too. – Guillaume Racicot Sep 08 '17 at 18:50
  • @Kane Because, as the question explains, this pattern occurs over and over in different functions, and it seems likely that `funcT` will often be generic. In which case writing the switch case is just boilerplate and maintenance burden that is of no value. – Nir Friedman Sep 08 '17 at 18:50
  • @NirFriedman, I understand this. But how does exactly `std::conditional` (as it is used in the question) eliminate these issues? – Kane Sep 08 '17 at 19:02
  • @Kane Because it casts the void* into the correct type, which then allows a generic function to act on it correctly? – Nir Friedman Sep 08 '17 at 19:09
  • @Kane. I think both yes and no. Yes because (if this worked) there would be less code to write. For example, I wouldn't have to call funcT for every single dtype possibility as I would have to do within a switch. No because I'm still looking for something more generalized that can be declared outside of func, that can use dtype to perform the correct static cast on data. I think one of the replies below starts addressing that. – wolfblade87 Sep 08 '17 at 19:11
  • The dynamic type identification is a **design smell** (or stench). You really need to deal with that at the design level. Not by a technical workaround. – Cheers and hth. - Alf Sep 08 '17 at 19:17
  • @NirFriedman, I think we are speaking about different things. Anyway, you use `switch` in your generic function below, not `std::conditional` :) – Kane Sep 08 '17 at 19:37
  • @Kane Yes, but I use switch once. You can write as many visitors as you want, and in particular, you can write as many generic visitors as you want, and never write another switch. The OP's concern was having to write switch over and over, and then perhaps change it in many places when a new type was added. – Nir Friedman Sep 08 '17 at 19:48
  • For me this is badly asked question (you are asking how to fix your solution of some problem). Most important information is reduced to almost nothing: `called from Python via ctypes.`. This part gives some hint what actual problem is. I recommend to edit question and describe that part in more details, I'm petty sure someone will give you nice patter to solve that problem, but more details are needed. I wouldn't be surprised that in the end `std::conditional` will turn out to be obsolete and misleading. – Marek R Sep 08 '17 at 22:22

5 Answers5

5

All types must be resolved at compile time. So no type, can ever depend on a runtime parameter to a function. The way to handle something like this is basically to build a visiting mechanism, once, and then you can reuse it. Basically, something like this:

template <class F>
void visit_data(void* data, const int dtype, F f) {
    switch (dtype)
    case 0: f(*static_cast<float*>(data));
    case 1: f(*static_cast<double*>(data));
}

Now you can implement functions by writing visitors:

struct func_impl {
    void operator()(float&) { ... }
    void operator()(double&) { ... }
};

Your visitor can also use generic code:

struct func_impl2 {
    template <class T>
    void operator()(T&) { ... }
};

Then you can write your function by leveraging the visitor:

void func(void* data, const int dtype) {
    visit_data(data, dtype, func_impl{});
}

The switch case over your list of types will only appear once in your entire codebase. If you add a new type, any visitor that doesn't handle it will give a compile time error if used.

You can also use lambdas to do it inline with a helper function or two; especially useful in 14 where you have generic lambdas.

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
4

If you can use C++17 it can be solved with a std::visitor and std::variant like so:

using var_t = std::variant<float, double>;
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

void func(var_t arg) {
    std::visit(overloaded {
            [](float  arg) { foo_float(arg); },
            [](double arg) { foo_double(arg); },
    }, arg);
}
aerkenemesis
  • 672
  • 4
  • 16
  • boost.variant can be used for pre C++17 – Slava Sep 08 '17 at 18:43
  • Normally I'd be all for using a variant, but the signature given in the question likely is required by ctypes, so I don't think this will work for this particular problem. – Nir Friedman Sep 08 '17 at 18:47
3

I'll start with a answer then detail how to downgrade .

Suppose you have a list of types:

template<class...>
struct types{using type=types;};

const types<int, double, char> supported_types;

Next we write some utility functions

template<std::size_t I, class...Ts>
using get_type = std::decay_t<decltype(std::get<I>(std::declval<std::tuple<Ts...>&>()))>;
template<std::size_t I, class Types>
struct type_at_helper;
template<std::size_t I, class...Ts>
struct type_at_helper<I, types<Ts...>>{
  using type=get_type<I,Ts...>;
};
template<std::size_t I, class Types>
using type_at = typename type_at_helper<I,Types>::type;

Now, type_at<2, decltype(supperted_types)> is char.

namespace helper {
  template<class F>
  using invoker = void(*)(F&&, void*);
  template<class F, class Types, std::size_t I>
  invoker<F> get_invoker() {
    return [](F&& f, void* pdata) {
      std::forward<F>(f)( static_cast<type_at<I, Types>*>(pdata) );
    };
  }
  template<class F, class Types, std::size_t...Is>
  void dispatch( F&& f, void* data, unsigned type_index, std::index_sequence<Is...>, Types ={} ) {
    using pF=std::decay_t<F>*;
    using invoker = void(*)(pF, void*);
    static const invoker table[]={
      get_invoker<F, Types, Is>()...
    };
    table[type_index]( std::forward<F>(f), data );
  }
}

template<class F, class...Ts>
void dispatch( F&& f, void* data, unsigned type_index, types<Ts...> {} ) {
  details::dispatch( std::forward<F>(f), data, type_index, std::make_index_sequence<sizeof...(Ts)>{}, types<Ts...>{} );
}

and done.

The downgrade to simply write make_index_sequence and index_sequence. Here is a high quality one, but there are easier ones out there.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
2

Is there a way something like std::conditional can done during run time, making use of the dtype identifier passed in?

No, there isn't. A run time value cannot be used to make type based decisions at compile type.

Given your post, the simplest solution is to use an if statement.

void func(void *data, const int dtype)
{
   if ( dtype == 0 )
   {
      funcT(static_cast<float*>(data));
   }
   else
   {
      funcT(static_cast<double*>(data));
   }
}

To be able to deal with lots of such functions, I would recommend using std::map<int, std::function<void(void*)>>.

Here's a simple program that compiles and builds for me.

#include <map>
#include <functional>

void funcT(float* data)
{
}

void funcT(double* data)
{
}

struct MyType {};

void funcT(MyType* data)
{
}

void func(void *data, const int dtype)
{
   std::map<int, std::function<void(void*)>> functions =
   {
      {0, [](void* in) {funcT(static_cast<float*>(in));}},
      {1, [](void* in) {funcT(static_cast<double*>(in));}},

      // ...

      {7, [](void* in) {funcT(static_cast<MyType*>(in));}}
   };

   if ( functions[dtype] != nullptr )
   {
      functions[dtype](data);
   }
}

int main(){}

One advantage of using the lambda functions is that you are free to call differently named functions for the various types. For example, you have option of using:

void foo(MyType* data) {}

and

{7, [](void* in) {foo(static_cast<MyType*>(in));}}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • he says there are 8 possible `dtype`, so a `switch` is better than `if`. But it seems like he already knows how to do that, he's looking for something less verbose. – Barmar Sep 08 '17 at 18:57
  • Pretty much this. There's a lot of code, and I'm looking for something that can take in the dtype, and return a static cast pointer. I'm not too experienced with C++, and so far in my Googling, a nested std::conditional was the closest thing (though obviously not valid) to what I'm trying to accomplish. I haven't reviewed the other replies yet so I will do that now. I really appreciate everyone's help so far. – wolfblade87 Sep 08 '17 at 19:05
  • @wolfblade87, you cannot get a different type of pointer from a function based on a run time value. – R Sahu Sep 08 '17 at 19:14
0

My solution to the problem would be a generic selectFunc() function which would select a function from a provided function set FS based on dtype, and return it:

using FuncType = void(*)(void*);

template<typename FS>
FuncType selectFunc(int dtype);

The function set would be a class with static handle() methods which would accept different types and a static fallback() method which would be called if dtype is not valid.

Example Usage:

struct FuncSet
{
    static void fallback() {};
    static void handle(float*) {};
    static void handle(double*) {};
};

void func(void *data, int dtype)
{
    // select a function from FuncSet based on dtype:
    auto f = selectFunc<FuncSet>(dtype);
    // invoke the selected function with the provided data:
    f(data);
    // note, two lines above could be combined into one line
}

Implementation:

// Static method which would call the correct FS::handle() method
template<typename FS, typename T>
struct Helper
{
    static void dispatch(void *data) {  FS::handle(static_cast<T*>(data)); }
};

// Static method which would call FS::fallback()
template<typename FS>
struct Helper<FS, void>
{
    static void dispatch(void*) { FS::fallback(); }
};

template<typename FS>
FuncType selectFunc(int dtype)
{
    switch ( dtype ) {
    case 0:  return &Helper<FS, float>::dispatch;
    case 1:  return &Helper<FS, double>::dispatch;
    // ... add other types here ...
    default: return &Helper<FS, void>::dispatch; // call fallback()
    }
}
Kane
  • 5,595
  • 1
  • 18
  • 27