Reputation: 356
I am experimenting on serialization/deserialization with template, and have now something working. Obviously, when implementing it, I ran into many troubles with hundreds of compiler error logs. Before extending my library further, I would like to secure it a bit, using SFINAE (which I only used scarcely so far) and static_asserts.
Not to mess up any further with my lib, I'm training in a sandbox :
http://coliru.stacked-crooked.com/a/9eb4eaefaac90fc0
I want to define a few predicates :
I want to be able to use those predicates for both SFINAE specialization and static_assert.
#include <sstream>
#include <iostream>
//forward declaration
class Base;
//"Template typedef" to check predicates
template <typename T>
using is_a_Base = typename std::enable_if<std::is_base_of<Base, T>::value, void>::type;
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
//Test classes
class Base{
public:
std::string getStr(){ return "Base.getStr()";}
};
class Derived: public Base {};
class Other{};
//A template class with its specializations with SFINAE
template <typename T, typename Enable = void>
class C{
public:
static void f(T& o){
std::cout << "f<T> default !" << std::endl;
}
};
template<typename T>
class C<T, is_a_Base<T>>
{
public:
static void f (T& o)
{
std::cout << "f<is_a_A>() ! " << o.getStr() << std::endl;
}
};
template<typename T>
class C<T, is_insertable<T> >
{
public:
static void f (T& o)
{
std::cout << "f<is_insertable() ! " << o << std::endl;
}
};
template<typename T>
std::string g(T& ref)
{
//static_assert(is_a_Base<T>, "T is not a Base"); //can't figure out the syntax here
return ref.getStr();
}
int main(){
Base a;
Derived b;
int myint = 1;
std::string str="toto";
Other oops;
C<Base>::f(a);
C<Derived>::f(b);
C<int>::f(myint); //Not calling is_insertable ??
C<std::string>::f(str); //Not calling is_insertable ??
C<Other>::f(oops);
std::cout << "g:" << g(a) << std::endl;
//std::cout << "g:" << g(oops) << std::endl; //should be blasted by the static assert
}
Results :
f<is_a_A>() ! Base.getStr()
f<is_a_A>() ! Base.getStr()
f<T> default !
f<T> default !
f<T> default !
g:Base.getStr()
So far, is_a_base is working for SFINAE. However, is_insertable is not working for the int and string variables ? Also I could not figure out how to properly reuse the is_a_base predicate into my assert statement.
(Since I am constrained with a cross-compiler not supporting beyond C++11, I can't use benefits from C++14 and beyond.)
Upvotes: 2
Views: 255
Reputation: 63124
You're using SFINAE to select a specialization of C
through the default argument void
. This means that the specialization's signature must substitute correctly, but also produce void
for the specialization to be selected. As it stands, your stream traits:
template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});
... actually produce the return type from operator >>
(resp. <<
), typically an is &
(resp. os &
). Your naming is a bit confusing as well since is_...
implies a boolean value, which they are not. Hence my suggested fix:
template
using enable_if_base = typename std::enable_if<std::is_base_of<Base, T>::value>::type;
// `void` is the default already ^
template <typename T, typename is = std::istream>
using enable_if_extractable = decltype (std::declval<is &>() >> std::declval<T &>(), void());
// ^^^^^^^^
template <typename T, typename os = std::ostream>
using enable_if_insertable = decltype (std::declval<os &>() << std::declval<T const &>(), void());
// ^^^^^^^^
Note that I also replaced T{}
with appropriately-qualified std::declval
s, since you cannot assume that the relevant types are actually default-constructible — in fact, std::istream
and std::ostream
are not.
Upvotes: 3
Reputation: 96800
Two issues:
std::ostream
and std::istream
do not have default constructors so creating them the way you did caused a substitution failure and so the primary template was used as the fallback. Use declval<T&>
in order to avoid creating a temporary with the default constructor.
The second template argument in the primary template must be void
in order for it to be selected. As you have it, it returns the type of the result of [i/o]s{} << T{}
(sic) which will be std::ostream&
, and std::istream&
for the other. You can fix this up by wrapping the second argument in a std::void_t
.
#1
template <typename T, typename is = std::istream>
using is_extractable = decltype (std::declval<is&>() >> T{});
template <typename T, typename os = std::ostream>
using is_insertable = decltype (std::declval<os&>() << T{});
#2
template<typename T>
class C<T, std::void_t<is_a_Base<T>>>
template<typename T>
class C<T, std::void_t<is_insertable<T>>>
Upvotes: 2