Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

choosing appropriate specialized template at runtime

I am using a class from a 3rd party library which look like,

template <typename A = DefaultT, typename B = DefaultT, typename C = DefaultT, typename D = DefaultT, typename E = DefaultT, typename F = DefaultT>
class Vertex {};

I want to use a partial specialization of this class at runtime depending on conditions, for example,

class MyA {};
class MyB {};
class MyC {};
class MyD {};
bool useA, useB, useC, useD; //these booleans change at runtime
// I want to specialize Vertex depending on the above booleans
// The below line shouldn't compile, this is just to give an idea
typedef typename Vertex <useA ? MyA : DefaultT,  useB ? MyB : DefaultT,  
                         useC ? MyC : DefaultT,  useD ? MyD : DefaultT> MyVertex;

I want to conditionally choose which template arguments I want to specialize. I am not sure if there's a term for this problem, I doubt it's a flavor of multiple dispatch.

A simple way would be to write 15 (2^4 -1) classes like,

typedef typename Vertex <MyA> MyVertexWithA;
typedef typename Vertex <DefaultT, MyB> MyVertexWithB;
typedef typename Vertex <MyA, MyB> MyVertexWithAB; //and so on...until
typedef typename Vertex <MyA, MyB, MyC, MyD> MyVertexWithABCD;

The problem becomes more complicated because I have to use a 'Mesh' class which uses the specialized vertex class

template <typename VertexClass, typename Others>
class Mesh {};

Now if I went down the path of writing 15 classes then I would have to write 15 more lines for each different mesh type. And it keeps getting more complicated where Mesh class is used.

I strongly believe this has to be done by either me or the compiler. My questions:

  1. I want to know if there's a way to make the compiler do this work for me?
  2. Does C++11 have a better mechanism to handle this scenario? Thanks.
like image 671
Chenna V Avatar asked Jan 08 '23 10:01

Chenna V


2 Answers

The answer is NO. If the conditions change at runtime, then you cannot specialize/instantiate templates based on those conditions, they need to be constant expressions (after all, the compiler performs this work at compiler time, before the program starts running, so using run-time expressions is a no-no). If they are determined at compile time, then you can use constexpr tricks in combination with std::conditional.

As @Karloy Horvath mentioned, you can also do something called tag dispatching, similar to the example below:

#include <iostream>

struct tag_yes{};
struct tag_no{};

template <typename T> void f_tag();

template <> 
void f_tag<tag_yes>() // specializations 
{
    std::cout << "YES" << std::endl;
}

template <> 
void f_tag<tag_no>()
{
    std::cout << "NO" << std::endl;
}

void f(bool condition)
{
    if(condition)
        f_tag<tag_yes>(); // call the YES specialization
    else
        f_tag<tag_no>(); // call the NO specialization
}

int main()
{
    bool condition = true;
    f(condition); // dispatch to the "right" template function
}

or, you can even deduce the tag (that's how standard C++ algorithms work with various iterator types)

#include <iostream>
#include <type_traits>

struct Foo
{
    using tag = std::true_type;
};

struct Bar
{
    using tag = std::false_type;
};

template <typename T> void f_tag();

template <> 
void f_tag<std::true_type>() // specializations 
{
    std::cout << "YES" << std::endl;
}

template <> 
void f_tag<std::false_type>()
{
    std::cout << "NO" << std::endl;
}

template <typename T>
void f(const T&)
{
        f_tag<typename T::tag>(); // dispatch the call
}

int main()
{
    Foo foo;
    Bar bar;

    f(foo); // dispatch to f_tag<std::false_type>
    f(bar); // dispatch to f_tag<std::true_type>
}

However, in your case polymorphism is probably the way to go, via a factory method and a common virtual interface.

like image 120
vsoftco Avatar answered Jan 29 '23 22:01

vsoftco


Well "template specialization at runtime" is an absolute no-go. Compiler processes templates during compilation, generating classes that are compiled to binary code.

If compiler does not produce it, you simply have no appropriate binary code to call, there fore you cannot instantiate such a class during run-time.

You need to go with run-time polymorphism. Either built-in virtual mechanism or simple plugins.

For example your Vertex has a property InterfaceA and in the constuctor, by default you use DefaultA which implements/derives from InterfaceA. But you could pass some other CustomA which also derives from InterfaceA. There is no going around this. You can't have goodness of compile-time mechanisms based on choices done at run-time.

EDIT: If you have all the classes and need to choose appropriate version during runtime. Then it is quite logical that there is some common interface in vertices that Mesh uses, therefore they should derive from a common class.

So you need to make a factory to create an object of appropriate type cast it to VertexInterface and return it.

VertexInterface makeVertex(bool useA, bool useB){
  if(useA && useB) return VertexInterface<MyA, MyB>();
  if(useA && !useB) return VertexInterface<MyA, DefaultT>();
  if(!useA && useB) return VertexInterface<DefaultT, MyB>();

  // default case
  return VertexInterface<DefaultT, DefaultT>();
}

You need the factory to handle all the (un)supported cases. Unfortunately the dispatch needs to be done manually, this is the bridge between templates and run-time.

like image 43
luk32 Avatar answered Jan 29 '23 21:01

luk32