if does not cause a compile-time branch. Both branches must be valid regardless of the result of the condition.
Here is my dispatch function I find useful in these cases:
template<std::size_t I> using index_t = std::integral_constant<std::size_t, I>; constexpr index_t<0> dispatch_index() { return {}; } template<class B0, class...Bools, class=std::enable_if_t<B0::value> > constexpr index_t<0> dispatch_index(B0, Bools...) { return {}; } template<class B0, class...Bools, class=std::enable_if_t<!B0::value> > constexpr auto dispatch_index(B0, Bools...bools) { return index_t< dispatch_index(bools...)+1 >{}; } template<class...Bools> constexpr auto dispatch( Bools...bools ) { using get_index = decltype(dispatch_index(bools...)); return [](auto&&...args){ using std::get; return get< get_index::value >( std::forward_as_tuple( decltype(args)(args)... ) ); }; }
This utility function does compile-time dispatching between a set of options.
Here is an example:
union bob {}; bob b; dispatch( std::is_arithmetic<decltype(b)>{} ) ( [&](auto&& value) { std::cout << value << "\n"; }, [&](auto&& value) { std::cout << "not a number\n"; } ) (b);
dispatch( std::is_arithmetic<decltype(b)>{} ) takes a truthy or falsy type by value (actually any number of them). It finds the first truthy type passed to it.
It then returns a lambda that takes any number of arguments, and returns the one corresponding to the first truthy argument to dispatch. If dispatch has no truthy arguments, it pretends it had a truthy argument "after the end" and dispatches based on that.
dispatch( std::is_arithmetic<decltype(b)>{} )
As bob is not is_arithmetic, this is a falsy type. So dispatch will return a lambda returning its second argument.
( [&](auto&& value) { std::cout << value << "\n"; }, [&](auto&& value) { std::cout << "not a number\n"; } )
In this case we pass it two lambdas, both with the same signature.
We are going to return the 2nd one.
(b);
We then pass it b. The first lambda, which expects the value to be passed to ostream&::operator<<, is never evaluated, so the fact that union bob doesn't support it doesn't matter.
live example, and using MSVC2015.
The above is valid C++14, and I believe I avoided all of the MSVC foibles that prevent it from compiling it.
To pass a type into your lambdas, you might want to use these:
template<class T>struct tag_t{using type=T; constexpr tag_t(){};}; template<class T>constexpr tag_t<T> tag{}; template<class Tag>using type_t = typename Tag::type; #define GET_TAGGED_TYPE(...) type_t< std::decay_t<decltype(__VA_ARGS__)> >;
then your code looks like:
dispatch(std::is_arithmetic<loggableType>{}) ( [&](auto tag){ using loggableType=GET_TAGGED_TYPE(tag); if (destination == DESTINATIONS::CONSOLE) { *printStreamPttr << "logging " << typeid(loggableType).name() << " with value " << dataLogging << '\n'; ConsolePrinter::OutputText(printStreamPttr); } else { logFileStream.open(logFilePath); logFileStream << "logging " << typeid(loggableType).name() << " with value " << dataLogging << '\n'; logFileStream.close(); } }, [&](auto non_arith_tag) { // nothing? } ) ( tag<loggableType> );
ifis a runtime construct, not compile-time. Code still has to be well-formed even if it is dead. For fixes, search for something like "static if emulation" or "tag dispatch".