Reputation: 195
I am designing an utility header that pumps binary data off an sf::InputStream
. For ease of use, is comprises a single function name, readFromStream
, that has a lot of (templated and non-templated) overloads for automatically deserializing standard-layout types and type compounds like vectors, tuples and my custom-designed grid
class. The complete implementation can be found here: https://github.com/JoaoBaptMG/ReboundTheGame/blob/master/MainGame/utility/streamCommons.hpp
So, I have defined an overload readFromStream
that pumps out a vector of any type by calling readFromStream
again recursively:
template <typename T, typename std::enable_if<!is_optimization_viable<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, std::vector<T> &value)
{
size_t size;
if (!readFromStream(stream, VarLength(size)))
return false;
std::vector<T> newVal(size, T());
for (auto &val : newVal)
if (!readFromStream(stream, val))
return false;
newVal.swap(value);
return true;
}
I'd like to write an optimized version for standard-layout classes for that there's not an overload for readFromStream
, so we can exploit the memory layout of them and blit them in a single read
call:
// trait is_optimization_viable is what I'm having trouble to write
template <typename T, typename std::enable_if<is_optimization_viable<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, std::vector<T> &value)
{
size_t size;
if (!readFromStream(stream, VarLength(size)))
return false;
std::vector<T> newVal(size, T());
if (stream.read(newVal.data(), size*sizeof(T)) != size*sizeof(T))
return false;
newVal.swap(value);
return true;
}
Well, I could use a solution described on other answers to detect presence of a function, but there's a catch. When the type is standard-layout, I have a default readFromStream
that reads like this:
template <typename T, typename std::enable_if<std::is_standard_layout<T>::value, int>::type = 0>
bool readFromStream(sf::InputStream &stream, T& value)
{
return stream.read((void*)&value, sizeof(T)) == sizeof(T);
}
So, there's always a function that does the serialization, not just the one I wanted. The problem I want to solve here is: how can I detect the presence of a non-default readFromString
for type T
, in order to disable the optimized version of readFromString
for std::vector<T>
?
I have tried to pull a few tricks. I can't limit the optimization to POD types because I'm using sf::Vector2<T>
on some types I want to deserialize, which is not POD. I tried to compare the function addresses I get when I use a non-templatized and templatized function, like:
using FPtr = bool(*)(sf::InputStream&, T&);
return (FPtr)readFromStream == (FPtr)readFromStream<T>;
But, strangely enough, it didn't work. And I researched a lot of solutions, but none I could adapt to what I needed. Maybe it's not possible in C++, and I'll have to resort "marking" the types I don't want to be optimized. Or maybe it's some obscure template I haven't thought of. How could I do this?
Upvotes: 1
Views: 316
Reputation: 61232
As I understand it your problem is:
is_optimization_viable<T>;
could be defined by:
template<typename T>
using is_optimization_viable<T> = std::is_standard_layout<T>;
but for the fact that, for certain values of T
that are standard layout
you nonetheless require a custom bool readFromStream(sf::InputStream &stream, T &value)
,
overload which means they are not optimization-viable.
Well as you must write these custom overloads, you know what those
exceptional values of T
are. Say they are types X
, Y
, Z
.
Then you can define the trait as:
#include <type_traits>
template<typename T, typename ...Us>
struct is_one_of;
template<typename T>
struct is_one_of<T> {
static constexpr bool value = false;
};
template<typename T, typename First, typename ...Rest>
struct is_one_of<T,First,Rest...> {
static constexpr bool value =
std::is_same<T,First>::value || is_one_of<T,Rest...>::value;
};
// ^ C++17: `std::disjunction` does the job
template<typename T>
using has_custom_read_from_stream = is_one_of<T,X,Y,Z>;
template<typename T>
struct is_optimization_viable {
static constexpr bool value = std::is_standard_layout<T>::value &&
!has_custom_read_from_stream<T>::value;
};
I appreciate that you'd rather avoid the ongoing maintenance of the
hard-coded type-list X
, Y
, Z
, and prefer somehow to SFINAE-probe
whether a call readFromStream(s, t)
will be a call to one of the
custom overloads for some std::declval
-ed s
and t
.
But that's a mirage. You tell us, there will be some overload
readFromStream(s, t)
that will compile whatever the type of t
.
If so, a SFINAE probe will always tell you that Yes, readFromStream(s, t)
will compile - for any T
as the unqualified type of t
. And you
still have to make a compiletime decision as to whether T
is one of
the custom types, and if not, whether it is standard-layout.
That's all there is to the problem. To tell whether T
is one of
the custom types you must either test it for identity with any one
of them disjunctively, as shown, or your must find a trait independent of their
identities that is satisfied by all and only the custom types. As you
don't tell us what those custom types are, I can't suggest any such trait,
but if you find one then it will define or replace has_custom_read_from_stream<T>
.
Incidentally, I second @NirFriedman's comment: is std::standard_layout
really what you mean?
Upvotes: 1