I'm a little... confused is maybe too strong a word for this, I just don't really grok this:
> As you can see, during serialization, it comes pretty handy to be able to check if an object has an attribute and to query the type of this attribute. In our case, it permits us to use the serialize method if available and fall back to the more generic method str otherwise.
I would actually prefer if, during serialization, the compiler would yell at me that object C doesn't have a serialize method. For one thing, it's very likely that, while it does have a toString() method, it doesn't have the corresponding fromString() method, making the serialized output useless. For the other, in a sane architecture, if an object does not have a serialize() method, then it's either: a) because it shouldn't be serialized in the first place, whatever the reason, or b) because it should have a serialize() method, but it has not yet been implemented (and it should!)
I understand that this is "didactic" example. Does anyone know of a more real-life/relevant example where this can be used? SFINAE (Substitution Failure Is Not An Error) looks neat as a language feature but I don't really see where it's useful (in fact, it looks like a bug that was repurposed as a feature).
The original purpose of SFINAE is to solve a critical problem--if you have multiple template versions of a function available, they don't all have to be valid for a given call. For example say you want to have a "first" operator that takes the first item of a container, but also works on individual values if they aren't containers:
template<class Container>
Container::value_type first(Container&& c) {
return *c.begin();
}
This call doesn't cause a compile error even though int::value_type does not exist--this is SFINAE. To make this work properly you'd also need to use compile-time introspection to disable the second function for types that have value_type, otherwise it's ambiguous.
> For example say you want to have a "first" operator that takes the first item of a container, but also works on individual values if they aren't containers:
But why would I want to do such a thing? Not only does first() not make sense for non-container objects -- but calling x.first() when x is a non-container object is a bug 9 times out of 10 (i.e. something got unpacked when it shouldn't have, an [x] (a single-object vector) ended up x (i.e. the first and only value of that vector).
SFINAE looks like a cheap way to sort of hack around lack of static duck typing. Otherwise these examples could be written in a positive way by saying a certain function only applies to a type containing certain members or constraints.
It's not really a bug, but a feature standardised in C++11 to avoid unwanted failures with a trailing template somewhere in your code. But this feature was hijacked to do type checks!
Maybe a better example would be a project that mix different libraries with different to convention for their "to string" methods: obj.toString(), to_string(obj), obj.ToString(), obj.to_string()... Which one would you use if you simply want to ouput your object on std::cout? In that case, a type check would be really handy!
One example where I've found SFINAE useful is in using "type traits" to tag different classes, and then writing containers to handle the classes differently based on their tags (see [1]).
For example, in arena-allocation support for protobufs, we wanted to be able to skip a destructor call to a message object if the object used only arena-allocated storage (hence had no cleanup to do). So we added a "DestructorSkippable_" typedef to any class where this was true:
class MyObject {
private:
friend class Arena;
typedef void DestructorSkippable_;
// ...
};
And then the SFINAE use at the implementation linked below [1] can either register a destructor callback, or not, depending on the class (by providing two overrides to a function, one that takes a T::DestructorSkippable_* param and one that doesn't).
The essential thing about this is that it's resolved at compile time, so there's zero overhead (not even a runtime conditional-check) in the destructor-skipped case. When optimizations are enabled, constant prop and dead-code elimination can completely remove a feature that's "disabled" by a certain type trait.
You can make a lot of `is_xyz` traits based on SFINAE. Like, `is_constructible` can be done this way. Some of these are less relevant with C++11 and `decltype` and all, but they've been extremely useful otherwise.
12 comments
[ 7.0 ms ] story [ 46.2 ms ] thread> As you can see, during serialization, it comes pretty handy to be able to check if an object has an attribute and to query the type of this attribute. In our case, it permits us to use the serialize method if available and fall back to the more generic method str otherwise.
I would actually prefer if, during serialization, the compiler would yell at me that object C doesn't have a serialize method. For one thing, it's very likely that, while it does have a toString() method, it doesn't have the corresponding fromString() method, making the serialized output useless. For the other, in a sane architecture, if an object does not have a serialize() method, then it's either: a) because it shouldn't be serialized in the first place, whatever the reason, or b) because it should have a serialize() method, but it has not yet been implemented (and it should!)
I understand that this is "didactic" example. Does anyone know of a more real-life/relevant example where this can be used? SFINAE (Substitution Failure Is Not An Error) looks neat as a language feature but I don't really see where it's useful (in fact, it looks like a bug that was repurposed as a feature).
template<class Container> Container::value_type first(Container&& c) { return *c.begin(); }
template<class ValueType> ValueType first(ValueType&& vt) { return vt; }
// Usage: first<int>(1); // 1
This call doesn't cause a compile error even though int::value_type does not exist--this is SFINAE. To make this work properly you'd also need to use compile-time introspection to disable the second function for types that have value_type, otherwise it's ambiguous.
But why would I want to do such a thing? Not only does first() not make sense for non-container objects -- but calling x.first() when x is a non-container object is a bug 9 times out of 10 (i.e. something got unpacked when it shouldn't have, an [x] (a single-object vector) ended up x (i.e. the first and only value of that vector).
Maybe a better example would be a project that mix different libraries with different to convention for their "to string" methods: obj.toString(), to_string(obj), obj.ToString(), obj.to_string()... Which one would you use if you simply want to ouput your object on std::cout? In that case, a type check would be really handy!
For example, in arena-allocation support for protobufs, we wanted to be able to skip a destructor call to a message object if the object used only arena-allocated storage (hence had no cleanup to do). So we added a "DestructorSkippable_" typedef to any class where this was true:
And then the SFINAE use at the implementation linked below [1] can either register a destructor callback, or not, depending on the class (by providing two overrides to a function, one that takes a T::DestructorSkippable_* param and one that doesn't).The essential thing about this is that it's resolved at compile time, so there's zero overhead (not even a runtime conditional-check) in the destructor-skipped case. When optimizations are enabled, constant prop and dead-code elimination can completely remove a feature that's "disabled" by a certain type trait.
[1] https://github.com/google/protobuf/blob/master/src/google/pr...