Default template specialization with multiple conditions - c++

I'd like to define functions for integral types, string and other types.
I can write:
template<typename T, typename = std::enable_if<std::is_integral<T>::value>::type>
void foo();
template<typename T, typename = std::enable_if<std::is_same<std::string>::value>::type>
void foo();
But how I can define function that will be called in other cases (if T not integral type and not std::string)?

I'm pretty sure that writing something like the line below becomes quite annoying and error-prone when you want to write up to N sfinae'd versions of foo:
std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
To avoid it, you can use the choice trick (a simple way to exploit overload resolution actually).
It follows a minimal, working example:
#include <iostream>
#include <utility>
#include <string>
template<int N> struct Choice: Choice<N-1> {};
template<> struct Choice<0> {};
template<typename T, typename... Args>
std::enable_if_t<std::is_integral<T>::value>
bar(Choice<2>, Args&&...) { std::cout << "integral" << std::endl; }
template<typename T, typename... Args>
std::enable_if_t<std::is_same<T, std::string>::value>
bar(Choice<1>, Args&&...) { std::cout << "string" << std::endl; }
template<typename T, typename... Args>
void bar(Choice<0>, Args&&...) { std::cout << "whatever" << std::endl; }
template<typename T, typename... Args>
void foo(Args&&... args) { bar<T>(Choice<100>{}, std::forward<Args>(args)...); }
int main() {
foo<bool>("foo");
foo<std::string>(42);
foo<void>(.0, "bar");
}
It handles nicely also the arguments that are directly forwarded to the right function once it has been picked up from the set.
The basic idea is that it tries to use all the versions of your function in the order you specified, from N to 0. This has also the advantage that you can set a priority level to a function when two of them match the template parameter T with their sfinae expressions.
The sfinae expressions enable or disable the i-th choice and you can easily define a fallback by using the Choice<0> tag (that is far easier to write than std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type).
The drawback (even though I wouldn't consider it a drawback) is that it requires an extra function that simply forwards the arguments to the chain by appending them to the Choice<N> tag.
See it up and running on Coliru.

Your examples do not compile at all for many reasons. See proper SFINAE in the code below. This is only one of many possible ways to do it.
You can just negate all special conditions simultaneously. For example:
template<typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Integral\n"; }
template<typename T, std::enable_if_t<std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Str\n"; }
template<typename T, std::enable_if_t<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>* = nullptr>
void foo() { std::cout << "Something else\n"; }
int main(void)
{
foo<int>();
foo<std::string>();
foo<float>();
return 0;
}
prints:
Integral
Str
Something else
Note that you may get automatic overload resolution if your functions take template-dependent arguments. SFINAE will look a bit different in this case:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<std::is_same<T, std::string>::value>::type foo(const T&) { std::cout << "Str\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
Usage:
foo(1);
foo(std::string("fdsf"));
foo(1.1f);
Finally, in the last case std::string overload may be on-template function:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type foo(const T&) { std::cout << "Integral\n"; }
template<typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_same<T, std::string>::value>::type
foo(const T&) { std::cout << "Something else\n"; }
void foo(const std::string&) { std::cout << "Str\n"; }

#Jarod42 was right.
I chose the wrong way to use SFINAE.
Actually, I had to use std::enable_if in return type declaration. And in order to define 'default' function (for all other types) I just need to define function with the same name and with ... as input parameters.
template<typename T>
std::enable_if_t<std::is_integral<T>::value>
foo() { std::cout << "integral"; }
template<typename T>
std::enable_if_t<std::is_same<T, std::string>::value>
foo() { std::cout << "string"; }
template<typename T>
void foo(...) { std::cout << "other"; }

Related

Is it possible to static_assert that a lambda is not generic?

I implemented a Visit function (on a variant) that checks that the currently active type in the variant matches the function signature (more precisely the first argument). Based on this nice answer.
For example
#include <variant>
#include <string>
#include <iostream>
template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);
template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);
template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));
std::variant<int, std::string> data="abc";
template <typename V>
void Visit(V v){
using Arg1 = typename std::remove_const_t<std::remove_reference_t<first_argument<V>>>;//... TMP magic to get 1st argument of visitor + remove cvr, see Q 43526647
if (! std::holds_alternative<Arg1>(data)) {
std::cerr<< "alternative mismatch\n";
return;
}
v(std::get<Arg1>(data));
}
int main(){
Visit([](const int& i){std::cout << i << "\n"; });
Visit([](const std::string& s){std::cout << s << "\n"; });
// Visit([](auto& x){}); ugly kabooom
}
This works, but it explodes with a user unfriendly compile time error when users passes a generic (e.g. [](auto&){}) lambda. Is there a way to detect this and give nice static_assert() about it?
Would also be nice if it worked with function templates as well, not just with lambdas.
Note that I do not know what possible lambdas do, so I can not do some clever stuff with Dummy types since lambdas may invoke arbitrary functions on types.
In other words I can not try to call lambda in 2 std::void_t tests on int and std::string and if it works assume it is generic because they might try to call .BlaLol() on int and string.
Is there a way to detect this and give nice static_assert about it?
I suppose you can use SFINAE over operator() type.
Follows an example
#include <type_traits>
template <typename T>
constexpr auto foo (T const &)
-> decltype( &T::operator(), bool{} )
{ return true; }
constexpr bool foo (...)
{ return false; }
int main()
{
auto l1 = [](int){ return 0; };
auto l2 = [](auto){ return 0; };
static_assert( foo(l1), "!" );
static_assert( ! foo(l2), "!" );
}
Instead of a bool, you can return std::true_type (from foo() first version) or std::false_type (from second version) if you want to use it through decltype().
Would also be nice if it worked with function templates as well, not just with lambdas.
I don't think it's possible in a so simple way: a lambda (also a generic lambda) is an object; a template function isn't an object but a set of objects. You can pass an object to a function, not a set of objects.
But the preceding solution should works also for classes/structs with operator()s: when there is a single, non template, operator(), you should get 1 from foo(); otherwise (no operator(), more than one operator(), template operator()), foo() should return 0.
Yet another simpler option:
#include <type_traits>
...
template <typename V>
void Visit(V v) {
class Auto {};
static_assert(!std::is_invocable<V, Auto&>::value);
static_assert(!std::is_invocable<V, Auto*>::value);
...
}
The Auto class is just an invented type impossible to occur in the V parameters. If V accepts Auto as an argument it must be a generic.
I tested in coliru and I can confirm the solution covers these cases:
Visit([](auto x){}); // nice static assert
Visit([](auto *x){}); // nice static assert
Visit([](auto &x){}); // nice static assert
Visit([](auto &&x){}); // nice static assert
I'm not sure if that would cover all the possible lambdas that you don't know which are :)
#include <variant>
#include <string>
#include <iostream>
template <class U, typename T = void>
struct can_be_checked : public std::false_type {};
template <typename U>
struct can_be_checked<U, std::enable_if_t< std::is_function<U>::value > > : public std::true_type{};
template <typename U>
struct can_be_checked<U, std::void_t<decltype(&U::operator())>> : public std::true_type{};
template<typename Ret, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...));
template<typename Ret, typename F, typename Arg, typename... Rest>
Arg first_argument_helper(Ret(F::*) (Arg, Rest...) const);
template <typename F>
decltype(first_argument_helper(&F::operator())) first_argument_helper(F);
template <typename T>
using first_argument = decltype(first_argument_helper(std::declval<T>()));
std::variant<int, std::string> data="abc";
template <typename V>
void Visit(V v){
if constexpr ( can_be_checked<std::remove_pointer_t<decltype(v)>>::value )
{
using Arg1 = typename std::remove_const_t<std::remove_reference_t<first_argument<V>>>;//... TMP magic to get 1st argument of visitor + remove cvr, see Q 43526647
if (! std::holds_alternative<Arg1>(data))
{
std::cerr<< "alternative mismatch\n";
return;
}
v(std::get<Arg1>(data));
}
else
{
std::cout << "it's a template / auto lambda " << std::endl;
}
}
template <class T>
void foo(const T& t)
{
std::cout <<t << " foo \n";
}
void fooi(const int& t)
{
std::cout <<t << " fooi " << std::endl;
}
int main(){
Visit([](const int& i){std::cout << i << std::endl; });
Visit([](const std::string& s){std::cout << s << std::endl; });
Visit([](auto& x){std::cout <<x << std::endl;}); // it's a template / auto lambda*/
Visit(foo<int>);
Visit<decltype(fooi)>(fooi);
Visit(fooi);
// Visit(foo); // => fail ugly
}
I don't know if it's you want, but you can, with that static_assert if an auto lambda is passed as parameter.
I think it's not possible to do the same for template function, but not sure.

Optimizing while using std::enable_if

Consider this code:
#include <iostream>
#include <type_traits>
template <std::size_t N> void bar() { std::cout << "bar<" << N << ">() called.\n"; }
template <std::size_t N> void hit() { std::cout << "hit<" << N << ">() called.\n"; }
template <typename T> struct evaluate : std::bool_constant<std::is_integral_v<T>> {
static constexpr std::size_t size = sizeof(T); // Simplified for illustration only.
};
void foo() { }
template <typename T, typename... Args>
std::enable_if_t<!evaluate<T>::value> foo (const T&, const Args&...);
template <typename T, typename... Args>
std::enable_if_t<evaluate<T>::value> foo (const T&, const Args&... args) {
bar<evaluate<T>::size>();
// Do whatever.
foo(args...);
}
template <typename T, typename... Args>
std::enable_if_t<!evaluate<T>::value> foo (const T&, const Args&... args) {
hit<evaluate<T>::size>();
// Do whatever, but different from the previous foo overload.
foo(args...);
}
int main() {
foo (5, "hello", true);
}
Output:
bar<4>() called.
hit<6>() called.
bar<1>() called.
How to rewrite the above so that evaluate<T> needs only be computed once instead of twice with each foo iteration?
You maybe like this one:
template <std::size_t N> void bar() { std::cout << "bar<" << N << ">() called.\n"; }
template <std::size_t N> void hit() { std::cout << "hit<" << N << ">() called.\n"; }
template <typename T>
struct evaluate : std::bool_constant<std::is_integral_v<T>>
{
static constexpr std::size_t size = sizeof(T); // Simplified for illustration only.
};
void foo() { }
template <typename T, typename... Args>
void foo( const T&, const Args&... args)
{
using X = evaluate<T>;
if constexpr ( X::value )
{
bar<X::size>();
}
else
{
hit<X::size>();
}
foo( args... );
}
int main() {
foo (5, "hello", true);
}
It "calls" only once evaluate<T>, which is not important but maybe easier to read. That all the template code is only used during instantiation makes it only a matter of taste.
As you mention c++17 you can use constexpr if to get rid of SFINAE at all in your example. This makes it also possible to reuse common lines of code in both variants of foo which is quite nice. The executable will not be much different you can believe, but the maintainability is much better I think!
Ok, I thought evaluate was computed twice. But how would you make it appear only once (without using macros)? We are supposed to avoid repetition in code anyways
You can try to save it as a additional template parameter with default value
Something as
template <typename T, typename... Args, typename E = evaluate<T>>
std::enable_if_t<!E::value> foo (const T&, const Args&...);
template <typename T, typename... Args, typename E = evaluate<T>>
std::enable_if_t<E::value> foo (const T&, const Args&... args)
{
bar<E::size>();
// Do whatever.
foo(args...);
}
template <typename T, typename ... Args, typename E>
std::enable_if_t<!E::value> foo (const T&, const Args&... args)
{
hit<E::size>();
// Do whatever, but different from the previous foo overload.
foo(args...);
}

Recursive type_traits for member function detection

I am trying to apply the type_trait has_fun recursively so that C enables its fun member function only if T has one.
Is there a way to make C::fun being conditionally detected?
template <typename T>
struct has_fun {
template <class, class> class checker;
template <typename U>
static std::true_type test(checker<U, decltype(&U::fun)> *);
template <typename U>
static std::false_type test(...);
static const bool value = std::is_same<std::true_type, decltype(test<T>(nullptr))>::value;
};
struct A {
void fun(){
std::cout << "this is fun!" << std::endl;
}
};
struct B {
void not_fun(){
std::cout << "this is NOT fun!" << std::endl;
}
};
template<typename T>
struct C {
void fun() {
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
int main(int argc, char const *argv[])
{
std::cout << has_fun<A>::value << std::endl;
std::cout << has_fun<B>::value << std::endl;
std::cout << has_fun<C<A>>::value << std::endl;
std::cout << has_fun<C<B>>::value << std::endl;
}
Output:
1
0
1
1
Expected output:
1
0
1
0
You need to allow the compiler to SFINAE on the method.
All checks that happen in templates only take into account the signatures of the functions, so the static_assert that you used will not be considered.
The solution is to add a check in the signature.
Intuitively you would write
template<typename T>
struct C {
std::enable_if_t<has_fun<T>::value> fun() {
t.fun();
}
T t;
};
But this will not produce what you expect: the compiler will refuse to compile C, even if you don't call C.fun();
Why?
The compiler is allowed to evaluate code and issue errors if it can prove it will never work.
Since when you declare C the compiler can prove that foo() will never be allowed, it will fail compilation.
To solve the issue you can force the method to have a dependent type, so that the compiler can't prove that it's always going to fail.
Here is the trick
template<typename T>
struct C {
template<typename Q=T, typename = if_has_fun<Q>>
void fun() {
t.fun();
}
T t;
};
The compiler can't prove that Q will always be T, and we check Q, not T, so the check is going to be performed only when you invoke fun.
Full working solution at https://wandbox.org/permlink/X32bwCqQDb288gVl
Note: I used the detector which is in experimental, but you can use your detector.
You need to replace the true test though, in order to check that the function can be properly called.
template <typename U>
static std::true_type test(checker<U, decltype(std::declval<U>().fun())> *);
See https://wandbox.org/permlink/MpohZzxvZdurMArP
namespace details{
template<template<class...>class,class,class...>
struct can_apply:std::false_type{};
template<template<class...>class Z,class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
}
template<template<class...>class Z,class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
template<class T, class...Args>
using dot_fun_r=decltype(std::declval<T>().fun(std::declval<Args>()...));
template<class T, class...Args>
using can_dot_fun = can_apply<dot_fun_r, T, Args...>;
can_dot_fun is a slicker version of has_fun.
temple<class U=T&,
std::enable_if_t< can_dot_fun<U>{}, bool > =true
void fun() {
static_cast<U>(t).fun();
}
Now C<B>{}.fun() is not valid, so can_dot_fun< C<B>> > is false.
This answer uses c++17 for brevity, but the pieces can be written as far back as c++11 (like void_t).
First of all, I propose you a little simplified version of your has_fun type traits
template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( &U::fun, std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};
This works to detect if for the type T is available one (and only one) member fun (&T::fun), regardless if it's a variable or a function, regardless the signature of the function (if it's a function).
Can be useful but consider that doesn't works when (1) there are more fun() overloaded methods and (2) when fun() is a template method.
Using this you can, by example, write (SFINAE enabling/disablig fun()) the C container as follows
template <typename T>
struct C
{
template <typename U = T>
auto fun() -> typename std::enable_if<has_fun<U>::value>::type
{
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
And this works, because you can write
C<A> ca;
ca.fun();
But if you try to print the has_fun<C<A>> value
std::cout << has_fun<C<A>>::value << std::endl;
you see that you get zero because the fun() function in C<A> is a template one.
Not only: if the fun() function in T ins't a void function, the line
t.fun();
in the C::fun() function, cause an error.
Suggestion: change your has_fun type traits to check, simulating a call with std::declval(), if T has a fun() method with a precise signature (void(*)(void), in your case)
template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( std::declval<U>().fun(), std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};
Now also has_fun<C<A>>::value is true because works also in case of overloading and template function; now the C::fun() method is safe because is enabled only if T has a fun() method with the correct signature.
The following is a full working example
#include <iostream>
#include <type_traits>
template <typename T>
struct has_fun
{
template <typename U>
static constexpr auto test (int)
-> decltype( std::declval<U>().fun(), std::true_type{} );
template <typename U>
static constexpr std::false_type test (...);
static constexpr bool value = decltype(test<T>(1))::value;
};
struct A
{ void fun(){ std::cout << "this is fun!" << std::endl; } };
struct B
{ void not_fun(){ std::cout << "this is NOT fun!" << std::endl; } };
template <typename T>
struct C
{
template <typename U = T>
auto fun() -> typename std::enable_if<has_fun<U>::value>::type
{
static_assert(has_fun<T>::value, "Not fun!");
t.fun();
}
T t;
};
int main ()
{
std::cout << has_fun<A>::value << std::endl;
std::cout << has_fun<B>::value << std::endl;
std::cout << has_fun<C<A>>::value << std::endl;
std::cout << has_fun<C<B>>::value << std::endl;
}
This could be done with two implementations of C, one with fun, the other without, and an extra std::enable_if_t artempte argument:
template<typename T, std::enable_if_t<has_fun<T>::value> * = nullptr>
struct C
{
void fun()
{ ... }
};
template<typename T, std::enable_if_t<!has_fun<T>::value> * = nullptr>
struct C
{
// no fun()
};
If most of C would actually be shared between the two cases you might also spin that shared part off into a base.

Is there an elegant solution for selecting between callable and non-callable type in a Variadic Template in C++14

I'm implementing simple text formatter for a class. The main function in it could receive a list of values that will be concatenated. Or optionally, for cases where the parameters are not friends of ostream, I accept a conversion function as first parameter that will convert all other parameters into a std::string.
The following code shows the idea, but it does not compile. For simplicity, I will output to cout in the example.
struct formater{
template<typename P, typename... PS>
void format(const P& p, const PS&... ps){
if (std::is_convertible<P, std::function<void()>>::value){
cout << p(ps...) << endl;
} else {
cout << p;
log(ps...);
}
}
};
The reason the code does not compile is, if P is callable, it will not be possible to output it to cout in the "else" branch, and if P is not callable, it will tell P is not callable and cannot receive ps... parameters in the "then" branch.
I thought to use enable_if, but since I'm defining both cases (T and F) of the condition, I get redefinition of same function and also fail to compile.
I could try to mimic static_if, but it doesn't look elegant at all.
I'm wondering if there is some elegant way to check that P is callable and SFINAE it. Maybe exploiting that I know the parameter types of P, (PS...)->std::string.
There are two problems. First, just like you said, an if is a runtime branch only, and second, you are not checking for an object to be callable, but you are checking if an object is callable with no arguments. Any callable object that must take arguments won't pass your test.
I would first implement that trait. Note that this is not necessary for C++17:
template<typename, typename = void>
struct is_callable : std::false_type {};
template<typename F, typename... Args>
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> : std::true_type {};
Then, you can use std::enable_if:
struct Formatter {
template<typename F, typename... Args>
auto format(F func, Args&&... args) -> std::enable_if_t<is_callable_v<F(Args...)>> {
std::cout << func(std::forward<Args>(args)...);
std::cout << std::endl;
}
template<typename T>
auto format(T&& value) -> std::enable_if_t<!is_callable_v<T()>> {
std::cout << std::forward<T>(value);
std::cout << std::endl;
}
};
You can implement void_t like this:
template<typename...>
using void_t = void;
You can go check this live example: Live at coliru
Please note that in C++17, you have the constexpr if and std::is_invocable:
struct Formatter {
template<typename F, typename... Args
void format(F&& param, Args&&... args) {
if constexpr (std::is_invocable_v<F, Args...>) {
std::cout << std::invoke(std::forward<F>(param), std::forward<Args>(args)...);
} else {
std::cout << std::forward<F>(param);
log(std::forward<Args>(args)...);
}
}
};
You could use std::enable_if:
struct formater{
template<typename P, typename... PS>
std::enable_if<std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type
format(const PARAM& p, const PS&... ps){
cout << p(ps...) << endl;
}
template<typename P, typename... PS>
std::enable_if<!std::is_convertible<PARAM, std::function<void(PS...)>>::value, void>::type
format(const PARAM& p, const PS&... ps){
cout << p;
log(ps...);
}
};

Template Function Specialization for Integer Types

Suppose I have a template function:
template<typename T>
void f(T t)
{
...
}
and I want to write a specialization for all primitive integer types. What is the best way to do this?
What I mean is:
template<typename I where is_integral<I>::value is true>
void f(I i)
{
...
}
and the compiler selects the second version for integer types, and the first version for everything else?
Use SFINAE
// For all types except integral types:
template<typename T>
typename std::enable_if<!std::is_integral<T>::value>::type f(T t)
{
// ...
}
// For integral types only:
template<typename T>
typename std::enable_if<std::is_integral<T>::value>::type f(T t)
{
// ...
}
Note that you will have to include the full std::enable_if return value even for the declaration.
C++17 update:
// For all types except integral types:
template<typename T>
std::enable_if_t<!std::is_integral_v<T>> f(T t)
{
// ...
}
// For integral types only:
template<typename T>
std::enable_if_t<std::is_integral_v<T>> f(T t)
{
// ...
}
I would use overload resolution. That spares you from having to use the gross SFINAE hack. Unfortunately there are many areas where you can't avoid it, but this fortunately isn't one of those.
template<typename T>
void f(T t)
{
f(t, std::is_integral<T>());
}
template<typename T>
void f(T t, std::true_type)
{
// ...
}
template<typename T>
void f(T t, std::false_type)
{
// ...
}
Using c++11, std::enable_if ( http://en.cppreference.com/w/cpp/types/enable_if ) can be used to do that:
template<typename T, class = typename std::enable_if<std::is_integral<T>::value>::type>
void f(T t) {...}
You can use a helper template that you can specialize like this:
#include <string>
#include <iostream>
#include <type_traits>
template <typename T, bool = std::is_integral<T>::value>
struct Foo {
static void bar(const T& t) { std::cout << "generic: " << t << "\n"; }
};
template <typename T>
struct Foo<T, true> {
static void bar(const T& t) { std::cout << "integral: " << t << "\n"; }
};
template <typename T>
static void bar(const T& t) {
return Foo<T>::bar(t);
}
int main() {
std::string s = "string";
bar(s);
int i = 42;
bar(i);
return 0;
}
output:
generic: string
integral: 42
Here is C++20 solution
template<std::integral T>
void f(T v) {
...
}
I think It's actually overloaded function but works the same.
What about a more straightforward and better readable way by just implementing the different versions inside the function body?
template<typename T>
void DoSomething(T inVal) {
static_assert(std::is_floating_point<T>::value || std::is_integral<T>::value, "Only defined for float or integral types");
if constexpr(std::is_floating_point<T>::value) {
// Do something with a float
} else if constexpr(std::is_integral<T>::value) {
// Do something with an integral
}
}
You dont have to worry about performance. The conditions are compile time constant and a descent compiler will optimize them away.
"if constexpr" is c++17 unfortunately but you may remove "constexpr" when both versions compile without errors for both types