Specify Template Parameters at Runtime

Specify template parameters at runtime

Here's what you can do:

MyClassInterface* Factor(int p1, int p2, int p3) {
if (p1 == 0 && p2 == 0 && p3 == 0)
return new MyClass<0,0,0>();
if (p1 == 0 && p2 == 0 && p3 == 1)
return new MyClass<0,0,1>();
etc;
}

Note that this does not even remotely scale to floating point values. It scales only to a known list of discrete values.


I've also used this bit of code before to do some template automatic generation:

#include <boost/preprocessor.hpp>

#define RANGE ((0)(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12))
#define MACRO(r, p) \
if (BOOST_PP_SEQ_ELEM(0, p) == var1 && BOOST_PP_SEQ_ELEM(1, p) == var2 && BOOST_PP_SEQ_ELEM(2, p) == var3 && BOOST_PP_SEQ_ELEM(3, p) == var4) \
actual_foo = foo<BOOST_PP_TUPLE_REM_CTOR(4, BOOST_PP_SEQ_TO_TUPLE(p))>;
BOOST_PP_SEQ_FOR_EACH_PRODUCT(MACRO, RANGE RANGE RANGE RANGE)
#undef MACRO
#undef RANGE

The compiler produces output that looks like this:

if (0 == var1 && 0 == var2 && 0 == var3 && 0 == var4) actual_foo = foo<0, 0, 0, 0>;
if (0 == var1 && 0 == var2 && 0 == var3 && 1 == var4) actual_foo = foo<0, 0, 0, 1>;
if (0 == var1 && 0 == var2 && 0 == var3 && 2 == var4) actual_foo = foo<0, 0, 0, 2>;
if (0 == var1 && 0 == var2 && 0 == var3 && 3 == var4) actual_foo = foo<0, 0, 0, 3>;
if (0 == var1 && 0 == var2 && 0 == var3 && 4 == var4) actual_foo = foo<0, 0, 0, 4>;
if (0 == var1 && 0 == var2 && 0 == var3 && 5 == var4) actual_foo = foo<0, 0, 0, 5>;
if (0 == var1 && 0 == var2 && 0 == var3 && 6 == var4) actual_foo = foo<0, 0, 0, 6>;
if (0 == var1 && 0 == var2 && 0 == var3 && 7 == var4) actual_foo = foo<0, 0, 0, 7>;
if (0 == var1 && 0 == var2 && 0 == var3 && 8 == var4) actual_foo = foo<0, 0, 0, 8>;
etc...

Also, please note that with this method, with 4 variables, each ranging over 13 values, You would cause the compiler to instantiate 28561 copies of this function. If your n was 50, and you still had 4 options, you would have 6250000 functions instantiated. This can make for a SLOW compile.

Parameter values in Template (C++): can be specified at runtime?

Yes. All template parameters need to be known at compile time (anything else will result in a compiler error).

The reason for this is that technically speaking, mysequence<int, 1> and mysequence<int, 2> are completely separate types, generated by the compiler. If the compiler has never seen your N before, how could it generate the types for you?

The fact that the compiler needs to generate each of those types separately is also what forces you to implement templates in the header. Determining parameters at runtime would obviously be even harder to do (you would need to dynamically recompile your code!).


Here is what the C++ standard has to say about template parameters (that are not types):

A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter

[temp.arg.nontype]/2

Now what is a converted constant expression?

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression

[some restrictions on the conversions that may be applied follow]

[expr.const]/5

So really, it's just a constant expression with some conversions added. The rules for what exactly is a constant expression are somewhat complex ([expr.const]/2), but the standard has a simple summary:

Expressions that satisfy these requirements, assuming that copy elision is performed, are called constant expressions.
[ Note: Constant expressions can be evaluated during translation. — end note ]

[expr.const]/1

Evaluated during translation is a fancy term for "the compiler can do it while compiling."


Therefore, non-type template parameters must be known at compile time (and so do type template parameters, although it is harder to construct a scenario where you would want to set type template parameters at runtime)

Select template argument at runtime in C++

No, you can't switch template arguments at runtime, since templates are instantiated by the compiler at compile-time. What you can do is have both templates derive from a common base class, always use the base class in your code, and then decide which derived class to use at runtime:

class Base
{
...
};

template <typename T>
class Foo : public Base
{
...
};

Base *newBase()
{
if(some condition)
return new Foo<float>();
else
return new Foo<double>();
}

Macros have the same problem as templates, in that they are expanded at compile-time.

Changing template arguments at runtime c++

This is not necessarily the best way to solve your problem. This is one way to generate a lot of instantiations in a compact manner. It may or may not suit your need.

I assume each of your classes have some kind of ID for run-time selection, say a string. So BronzeFork::id would be "BronzeFork". I will use abbreviated names henceforth.

  #include <tuple>
#include <string>
#include <functional>
#include <map>

struct F1 { static constexpr const char* id = "F1"; };
struct F2 { static constexpr const char* id = "F2"; };
struct F3 { static constexpr const char* id = "F3"; };
struct K1 { static constexpr const char* id = "K1"; };
struct K2 { static constexpr const char* id = "K2"; };
struct K3 { static constexpr const char* id = "K3"; };
struct S1 { static constexpr const char* id = "K1"; };
struct S2 { static constexpr const char* id = "K2"; };
struct S3 { static constexpr const char* id = "K3"; };

template <typename F, typename K, typename S> struct Eater
{
static void eat();
};

using t3 = std::tuple<std::string, std::string, std::string>;

std::map<t3, std::function<void()>> funcMap;

Now we need to populate funcMap. We need nested compile-time loops. I implement them as separate templates using fold-expressions. There might be a better way, but this one is very unsophisticated and straightforward so it's easy to understand.

  template <typename F, typename K, typename S> struct populate0
{
void operator()() { funcMap.insert({{F::id, K::id, S::id}, Eater<F, K, S>::eat}); }
};

// Inner loop
template <typename Fs, typename K, typename S> struct populate1;
template <typename ... Fs, typename K, typename S>
struct populate1 <std::tuple<Fs...>, K, S> // loop over Fs
{
void operator()() { (populate0<Fs, K, S>()(), ...); }
};

// Middle loop
template <typename Fs, typename Ks, typename S> struct populate2;
template <typename Fs, typename ... Ks, typename S>
struct populate2 <Fs, std::tuple<Ks...>, S> // loop over Ks
{
void operator()() { (populate1<Fs, Ks, S>()(), ...); }
};

// Outer loop
template <typename Fs, typename Ks, typename Ss> struct populate3;
template <typename Fs, typename Ks, typename... Ss> // loop over Ss
struct populate3 <Fs, Ks, std::tuple<Ss...>>
{
void operator()() { (populate2<Fs, Ks, Ss>()(), ...); }
};

Now we need to just run the loop.

  populate3<std::tuple<F1, F2, F3>, 
std::tuple<K1, K2, K3>,
std::tuple<S1, S2, S3>>()();

Whenever you add another class, add it to this call.

How to dynamically add template parameter

Is it possible to dynamically assign this template parameter(5) at run-time

Absolutely not. All template instantiation happens at compile time.

or is there a work-around?

Maybe.

You can certainly instantiate fooN for a bunch of different values, and choose between them at runtime. The simplest way is something like this

int algo(int n)
{
switch(n) {
case 3: return algoImpl<3>();
case 5: return algoImpl<5>();
default: return -1;
}
}

although you obviously need to get your parameters from somewhere. You can reasonably have each algoImpl<N> take a (runtime) vector of argument values, convert it into a tuple, and use std::apply to invoke your underlying function.

Currently I'm using CMake

Commiserations.

... to statically set the template parameter.

Eww. Storing bits of your type system in a build configuration file feels a bit hairy. What does your build system know about the correct value of N that your code doesn't?

Is it possible to cast template parameters on runtime?

It is not exactly clear from your question what type you expect T to be in this scenario, but if you want it to be e.g. the type of the first argument, then you can make the second parameter a non-deduced context:

template <typename T>
T max(T x, std::type_identity<T>::type y)
{
return (x > y)? x : y;
}

std::type_identity is a C++20 feature but its implementation is really simple:

template<typename T>
struct type_identity {
using type = T;
};

Everything left of the scope resolution operator in the parameter is non-deduced, so the whole parameter is non-deduced context and will not participate in template argument deduction. Instead normal implicit conversions will be applied in overload resolution.

Choose template based on run-time string in C++

enum class Type
{
Int,
String,
// ...
Unknown
};

Type TypeFromString(const std::string& s)
{
if (s == "int") { return Type::Int; }
if (s == "string") { return Type::String; }
// ...
return Type::Unknown;
}

template <template <typename> class>
struct base_of;

template <template <typename> class C>
using base_of_t = typename base_of<C>::type;

And then the generic factory

template <template <typename> class C>
std::unique_ptr<base_of_t<C>> make_templated(const std::string& typeStr)
{
Type type = TypeFromString(typeStr);
static const std::map<Type, std::function<std::unique_ptr<base_of_t<C>>()>> factory{
{Type::Int, [] { return std::make_unique<C<int>>(); } },
{Type::String, [] { return std::make_unique<C<std::string>>(); } },
// ...
{Type::Unknown, [] { return nullptr; } }
};
return factory.at(type)();
}

a specialization is needed for each base:

template <>
struct base_of<raw_attribute_vector> {
using type = base_attribute_vector;
};

And then

auto p = make_templated<raw_attribute_vector>(s);

Demo

Apply template function depending on runtime parameter

Syntax template<typename F<typename P>> is wrong.

Moreover, you cannot pass template function (I mean print and set T afterward).

But you can for class:

template <typename T>
struct printer
{
void operator()(const T& val) const
{
cout << val << endl;
}
}

template <template <typename> class C>
void apply_type_template_func()
{
C<int>{}(42);
C<float>{}(4.2f);
C<std::string>{}("Hello world");
}

That is why a way to pass template function is to wrap them in class or lambda:

auto lambda = [](const auto& arg){ print(arg); };

Note: Here, class (lambda) is not template, but its operator() is.

And indeed, to transform a runtime value into compile time value/type,
you have to dispatch at runtime (so with limited set of values) to compile time value.

As with switch:

template <template <typename> class C>
void apply_type_template_func(EType type)
{
switch (type)
{
case EType::INT: C<int>{}(42); break;
case EType::Double: C<double>{}(4.2); break;
case EType::STRING: C<std::string>{}("Hello world"); break;
}
}

Note that each branch should be valid, even if branch is not taken, so your variant with forwarding argument would be wrong, as all of C<int>{}(arg), C<double>{}(arg), C<std::string>{}(arg) should be valid (which is not the case for regular argument).



Related Topics



Leave a reply



Submit