ok, got a rad solution from the folks in C++Help(discord) that uses c++17 fold expressions:
template<typename... Types>
struct type_list {
template<typename T>
constexpr static bool contains = (... || std::is_same_v<T, Types>);
};
class IOUtils {
public:
template<typename T>
static T read(juce::InputStream &in)
{
static_assert(type_list<char, short, float, double, int>::contains<T>,
"T is not a readable type!" );
if constexpr( std::is_same<T, char>::value ) { return in.readByte(); }
if constexpr( std::is_same<T, short>::value ) { return in.readShort(); }
if constexpr( std::is_same<T, float>::value ) { return in.readFloat(); }
if constexpr( std::is_same<T, double>::value ) { return in.readDouble(); }
if constexpr( std::is_same<T, int>::value ) { return in.readInt(); }
//etc for every other InputStream::read___
/*
this should never be hit because the static_assert will fail for any
T that InputStream can't read, but it silences the compiler warning
that this function doesn't have a return value
*/
return {};
}
};
the static_assert will fire if you don’t pass a T that InputStream can deal with, and the if constexpr
will force the execution of the type comparison to happen at compile time, not run time, so the complaints about if else
branching are nullified. if constexpr(false)
doesn’t get compiled, so if your T is a float, the function looks like this:
float read(juce::InputStream& in)
{
return in.readFloat();
}
I stand corrected, it gets compiled into stuff like this:
/* First instantiated from: insights.cpp:54 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
static inline int read<int>(juce::InputStream & in)
{
/* PASSED: static_assert(type_list<char, short, float, double, int>::contains<int>, "T is not a readable type!"); */
if constexpr(std::integral_constant<bool, 0>::value) ;
if constexpr(std::integral_constant<bool, 0>::value) ;
if constexpr(std::integral_constant<bool, 0>::value) ;
if constexpr(std::integral_constant<bool, 0>::value) ;
if constexpr(std::integral_constant<bool, 1>::value) {
return in.readInt();
}
return {};
}
#endif
};
you can see more here at cppinsights.io
just paste this in:
#include <iostream>
#include <cstdlib>
#include <type_traits>
namespace juce
{
struct InputStream
{
char readByte() { return {1}; }
short readShort() { return {2}; }
float readFloat() { return {3}; }
double readDouble() { return {4}; }
int readInt() { return {5}; }
};
}
template<typename... Types>
struct type_list {
template<typename T>
constexpr static bool contains = (... || std::is_same_v<T, Types>);
};
class IOUtils {
public:
template<typename T>
static T read(juce::InputStream &in)
{
static_assert(type_list<char, short, float, double, int>::contains<T>,
"T is not a readable type!" );
if constexpr( std::is_same<T, char>::value ) { return in.readByte(); }
if constexpr( std::is_same<T, short>::value ) { return in.readShort(); }
if constexpr( std::is_same<T, float>::value ) { return in.readFloat(); }
if constexpr( std::is_same<T, double>::value ) { return in.readDouble(); }
if constexpr( std::is_same<T, int>::value ) { return in.readInt(); }
//etc for every other InputStream::read___
/*
this should never be hit because the static_assert will fail for any
T that InputStream can't read, but it silences the compiler warning
that this function doesn't have a return value
*/
return {}; //
}
};
int main()
{
juce::InputStream is;
std::cout << (int)IOUtils::read<char>(is) << std::endl;
std::cout << IOUtils::read<short>(is) << std::endl;
std::cout << IOUtils::read<float>(is) << std::endl;
std::cout << IOUtils::read<double>(is) << std::endl;
std::cout << IOUtils::read<int>(is) << std::endl;
//std::cout << IOUtils::read<unsigned int>(is) << std::endl;
}