1

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?

JoaoBapt
  • 195
  • 1
  • 11
  • First of all you need some complementary `std::enable_if` clauses, because `std::is_standard_layout` is probably not the opposite of `is_optimization_viable` and you have an ambiguity at your hands. – Henri Menke Apr 30 '17 at 21:53
  • @HenriMenke Yes, it's not the opposite; actually, the latter trait requires the former for what I need. I may not have described correctly what I want. – JoaoBapt Apr 30 '17 at 21:56
  • Why would you "disable the optimized version of readFromString"? If you want to check the presence of unusual serialization function then detection code would be basically the same as detection code checking for presence of usual serialization function, just with adjusted signature. – user7860670 May 01 '17 at 07:20
  • @VTT There are some standard-layout functions (like `std::string`, my own [`GameObjectDescriptor`](https://github.com/JoaoBaptMG/ReboundTheGame/blob/master/MainGame/data/RoomData.hpp) and basically everything with pointers) that are *not* supposed to be deserialized by fast blitting; so I want to check whether the presence of a particular overload exists so it's safer to use it. – JoaoBapt May 01 '17 at 11:14
  • Related: [Is it possible to write a template to check for a function's existence?](http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-template-to-check-for-a-functions-existence/264088) – user7860670 May 01 '17 at 13:37
  • Are you sure that maybe you just want to use the is_trivial trait instead of is_standard_layout? is_trivial determines whether it is safe to treat the class as a bag of bytes. is_standard_layout is basically useful for nothing other than ABI interop. – Nir Friedman May 01 '17 at 15:14

1 Answers1

1

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?

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
  • If it weren't for that pesky `sf::Vector2`, I think `is_trivial` or even `is_pod` would suffice, but it's a dependency I cannot change. – JoaoBapt May 01 '17 at 16:16