I would like to make a type trait for checking if a particular type is hashable using the default instantiations of the standard library's unordered containers, thus if it has a valid specialization for std::hash. I think this would be a very useful feature (e.g. for using std::set as failsafe for std::unordered_set in generic code). So I, thinking std::hash is not defined for each type, started making the following SFINAE solution:
template<typename T> std::true_type hashable_helper(
const T&, const typename std::hash<T>::argument_type* = nullptr);
template<typename T> std::false_type hashable_helper(...);
//It won't let me derive from decltype directly, why?
template<typename T> struct is_hashable
: std::is_same<decltype(hashable_helper<T>(std::declval<T>())),
std::true_type> {};
(Forgive my modest SFINAE-abilities if this is not the best solution or even wrong.)
But then I learned, that both gcc 4.7 and VC++ 2012 define std::hash for any type T, just static_asserting in the non-specialized version. But instead of compiling conditionally they (and also clang 3.1 using gcc 4.7's libstdc++) fail the assertion resulting in a compile error. This seems reasonable since I think static_asserts are not handled by SFINAE (right?), so an SFINAE solution seems not possibly at all. It's even worse for gcc 4.6 which doesn't even have a static_assert in the general std::hash template but just doesn't define its () operator, resulting in a linker error when trying to use it (which is always worse than a compile error and I cannot imagine any way to transform a linker error into a compiler error).
So is there any standard-conformant and portable way to define such a type trait returning if a type has a valid std::hash specialization, or maybe at least for the libraries static_asserting in the general template (somehow transforming the static_assert error into a SFINAE non-error)?
Since C++17 it is now possible to do this in a more elegant way.
From cppreference about std::hash:
Each specialization of this template is either enabled ("untainted") or disabled ("poisoned"). For every type Key for which neither the library nor the user provides an enabled specialization std::hash, that specialization exists and is disabled. Disabled specializations do not satisfy Hash, do not satisfy FunctionObject, and std::is_default_constructible_v, std::is_copy_constructible_v, std::is_move_constructible_v, std::is_copy_assignable_v, std::is_move_assignable_v are all false. In other words, they exist, but cannot be used.
This meant that the STL had to remove the static_assert in C++17. Here is a working solution with 'Clang-6.0.0 -std=c++17':
#include <functional>
#include <ios>
#include <iostream>
#include <type_traits>
template <typename T, typename = std::void_t<>>
struct is_std_hashable : std::false_type { };
template <typename T>
struct is_std_hashable<T, std::void_t<decltype(std::declval<std::hash<T>>()(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_std_hashable_v = is_std_hashable<T>::value;
struct NotHashable {};
int main()
{
std::cout << std::boolalpha;
std::cout << is_std_hashable_v<int> << std::endl;
std::cout << is_std_hashable_v<NotHashable> << std::endl;
return 0;
}
This might for example come in handy when you use boost::hash_combine or boost::hash_range. If you include a header containing the following code sample you do not need to define boost hashes for specific types anymore.
#include <boost/functional/hash_fwd.hpp>
template <typename T, typename = std::void_t<>>
struct is_boost_hashable : std::false_type { };
template <typename T>
struct is_boost_hashable<T, std::void_t<decltype(boost::hash_value(std::declval<T>()))>> : std::true_type { };
template <typename T>
constexpr bool is_boost_hashable_v = is_boost_hashable<T>::value;
namespace boost
{
template <typename T>
auto hash_value(const T &arg) -> std::enable_if_t<is_std_hashable_v<T> &&
!is_boost_hashable_v<T>, std::size_t>
{
return std::hash<T>{}(arg);
}
}
Notice the is_boost_hashable_v, this is necessary to avoid ambiguity as boost already provides hashes for a lot of hashes.
It seems we have two conflicting requirements:
SFINAE is meant to avoid any instantiation of a template if the instantiation might fail and remove the corresponding function from the overload set.
static_assert() is meant to create an error, e.g., during instantiation of a template.
To my mind, 1. clearly trumps 2., i.e., your SFINAE should work. From the looks of two separate compiler vendors disagree, unfortunately not between themselves but with me. The standard doesn't seem to specify how the default definition of std::hash<T> looks like and seems to impose constraints only for the cases where std::hash<T> is specialized for a type T.
I think your proposed type traits is a reasonable idea and it should be supported. However, it seems the standard doesn't guarantee that it can be implemented. It may be worth bringing this up with the compiler vendors and/or filing a defect report for the standard: The current specification doesn't give clear guidance what should happen, as far as I can tell. ... and if the specification currently mandates that a type traits as above fails it may be a design error which needs to be corrected.
Here is a VERY dirty solution to your problem: It works for GCC 4.7 (and not 4.6, due to missing C++11 feature: mangling overload)
// is_hashable.h
namespace std {
template <class T>
struct hash {
typedef int not_hashable;
};
}
#define hash hash_
#define _Hash_impl _Hash_impl_
#include<functional>
#undef hash
#undef _Hash_impl
namespace std {
struct _Hash_impl: public std::_Hash_impl_{
template <typename... Args>
static auto hash(Args&&... args)
-> decltype(hash_(std::forward<Args>(args)...)) {
return hash_(std::forward<Args>(args)...);
}
};
template<> struct hash<bool>: public hash_<bool> {};
// do this exhaustively for all the hashed standard types listed in:
// http://en.cppreference.com/w/cpp/utility/hash
}
template <typename T>
class is_hashable
{
typedef char one;
typedef long two;
template <typename C> static one test( typename std::hash<C>::not_hashable ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(long) };
};
// main.cpp
// #include "is_hashable.h"
#include<iostream>
#include<unordered_set>
class C {};
class D {
public:
bool operator== (const D & other) const {return true;}
};
namespace std {
template <> struct hash<D> {
size_t operator()(const D & d) const { return 0;}
};
}
int main() {
std::unordered_set<bool> boolset;
boolset.insert(true);
std::unordered_set<D> dset;
dset.insert(D());// so the hash table functions
std::cout<<is_hashable<bool>::value<<", ";
std::cout<<is_hashable<C>::value << ", ";
std::cout<<is_hashable<D>::value << "\n";
}
And the output is:
1, 0, 1
We basically "hijack" the hash symbol and inject some helper typedef in it. You'll need to modify it for VC++, in particular, the fix for _Hash_impl::hash() since it's an implementation detail.
If you make sure that the section labelled as is_hashable.h is included as the first include this dirty trick should work...
I hit this too. I tried a few workarounds and went with a whitelist filter for std::hash<>. the whitelist is not pleasant to maintain, but it is safe and it works.
I tried this on VS 2013, 2015, clang and gcc.
#include <iostream>
#include <type_traits>
// based on Walter Brown's void_t proposal
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3911.pdf
namespace detail {
template<class... TN> struct void_t {typedef void type;};
}
template<class... TN>
struct void_t {typedef typename detail::void_t<TN...>::type type;};
// extensible whitelist for std::hash<>
template <class T, typename = void>
struct filtered_hash;
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_enum<T>::value>::type>
: std::hash<T> {
};
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_integral<T>::value>::type>
: std::hash<T> {
};
template <class T>
struct filtered_hash<T,
typename std::enable_if<std::is_pointer<T>::value>::type>
: std::hash<T> {
};
template<typename, typename = void>
struct is_hashable
: std::false_type {};
template<typename T>
struct is_hashable<T,
typename void_t<
typename filtered_hash<T>::result_type,
typename filtered_hash<T>::argument_type,
typename std::result_of<filtered_hash<T>(T)>::type>::type>
: std::true_type {};
// try it out..
struct NotHashable {};
static_assert(is_hashable<int>::value, "int not hashable?!");
static_assert(!is_hashable<NotHashable>::value, "NotHashable hashable?!");
int main()
{
std::cout << "Hello, world!\n";
}
Related
Before that I want to tell that I have tried to implement is_assignable on my own. There is no need to show me another examples - I have already seen some implementation.
I would like to fix my solution thanks to you (if it's possible, of course) that'll work out.
So, here is my code:
#include <iostream>
#include <type_traits>
#include <utility>
template<typename LambdaT>
struct is_valid_construction {
is_valid_construction(LambdaT) {}
typedef typename LambdaT lambda_prototype;
template<typename ValueTypeT, typename ExprTypeT = decltype(std::declval<lambda_prototype>()(std::declval<ValueTypeT>()))>
struct evaluate {
evaluate(ValueTypeT val) {
std::cout << "Right!";
}
typedef typename std::true_type value;
};
template<typename ValueTypeT> //The compiler ignores this definition
struct evaluate<ValueTypeT, decltype(std::declval<lambda_prototype>()(std::declval<int>()))> {
evaluate(ValueTypeT val) {
std::cout << "Nope";
}
typedef typename std::false_type value;
};
template<typename ValueTypeT>
void print_value(ValueTypeT val) {
evaluate evaluation(val);
}
};
struct ForTest {};
int main() {
is_valid_construction is_assignable([](auto x) -> decltype(x = x) { });
is_valid_construction is_less_comparable([](auto x) -> decltype(x < x) {});
is_valid_construction is_more_comparable([](auto x) -> decltype(x > x) {});
is_assignable.print_value(int{});
is_less_comparable.print_value(char{});
is_more_comparable.print_value(ForTest{});
return 0;
}
As you can see I am trying to define template structure within template structure. So, I excepted that if the invocation (with declval) of this lambda-expression with parameter of this type (rougly, in terms of substitution) is failed, then SFINAE goes further and should see that the second template definition could be convenient for instantiation. I am asking how could I fix my template structure and its default parameter to push SFINAE use the second definition?
SFINAE can be used in order to direct the compiler to choose a particular function overload, or a particular partial specialization of a class template. In the first case, substitution failures remove declarations from the overload set and in the second case, substitution failures remove the partial specialization declarations from consideration (causing either the primary template to be used, or a different partial specialization for which substitution succeeds).
But what you are trying to do here is backward: you have a situation where the primary template is potentially subject to substitution error, and you provide a partial specialization as an alternative. This can never work. Partial specialization matching begins after the template argument list to the primary template is fully known, therefore if a substitution error occurs in the primary template's template argument list, no specializations can be considered.
For example if we have
template <typename T, typename U = some_metafunction_of_T>
struct S;
template <typename T>
struct S<T, T>;
then the instantiation process of S<int> will first evaluate U for the primary template, and then, only once T and U are both known, the compiler can determine whether or not they are the same (which would allow the partial specialization to be used). If a substitution error occurs while computing U, the question of whether the partial specialization applies cannot even be asked.
To fix your code, you would have to switch the two definitions of evaluate. The primary template would have to be the "fallback", and the partial specialization would have to be potentially subject to substitution error.
as #Brian said, you should put the requirements at the primary template if the requirements are for all specializations, and put other requirements for each specialization at their own declarations:
template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t</* requirements for this specialization */>>;
and if you want one of specialization is prior to others, you can add its negative requirements to other specializations:
template<typename T, typename = std::void_t</* global requirements */>>
struct S;
template<typename T>
struct S<T, std::void_t<std::enable_if_t</* conditions for this specialization */>>>;
template<typename T>
struct S<T, std::void_t<std::enable_if_t<!/* conditions for the former specialization */>, /* requirements for this specialization */>>;
for your example, it should be like this:
template<typename Lambda>
struct is_valid_construction{
template<typename T, typename = void>
struct helper : std::false_type{};
template<typename T>
struct helper<T, std::void_t<decltype(std::declval<Lambda>()(std::declval<T>()))>> : std::true_type{};
template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<helper<V>::value>>;
template<typename V>
struct evaluate<V, std::void_t<std::enable_if_t<!helper<V>::value>, decltype(std::declval<Lambda>()(std::declval<int>()))>>;
};
by the way, you can use std::is_invocable to simplify this code:
template<typename Lambda>
struct is_valid_construction{
template<typename V, typename = void>
struct evaluate;
template<typename V>
struct evaluate<V, std::enable_if_t<std::is_invocable_v<Lambda, V>>>;
template<typename V>
struct evaluate<V, std::enable_if_t<!std::is_invocable_v<Lambda, V> && std::is_invocable_v<Lambda, int>>>;
};
Thanks to #RedFog and #Brian I could complete my code and I have got the such result:
#include <iostream>
#include <type_traits>
#include <utility>
template<typename LambdaT>
struct is_valid_construction {
is_valid_construction(LambdaT) {}
typedef LambdaT lambda_prototype;
template<class ValueT, class = void>
struct is_void_t_deducable : std::false_type {};
template<class ValueT>
struct is_void_t_deducable<ValueT,
std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>> : std::true_type {};
template<class ValueT>
bool is_valid_for(ValueT value) {
if constexpr (is_void_t_deducable<ValueT>::value)
return true;
else
return false;
}
};
struct ForTest {};
int main() {
is_valid_construction is_assignable([](auto x) -> decltype(x * x) { });
std::cout << is_assignable.is_valid_for(0) << std::endl;
std::cout << is_assignable.is_valid_for(ForTest{});
return 0;
}
As they both said, that when I had declared template parameter like that:
template<typename ValueTypeT, typename ExprTypeT = decltype(std::declval<lambda_prototype>()(std::declval<ValueTypeT>()))>
the compiler didn't understand what a default value should the second template parameter assign and since both declarations are incompatible.
I am new one in template programming and I can try to explain the solution as simple as possible:
The second template parameter is (if to say not strictly!) should be void. So, the compiler can instantiate the template with second void parameter in two ways by means of first declaration or second declaration.
(It should be said that std::void_t<TemplateParam> becomes void if TemplateParam is well!)
If an instantiation with the second declaration is well, then the
second template parameter is void.
If an instantiation with the first declaration is well, then the
second template parameter is void.
So, we should help compiler to deduce both structures with the second template parameter void. When it tries to instantiate is_valid_for(ForTest{}) first of all it tries to deduce
std::void_t<decltype(std::declval<lambda_prototype>()(std::declval<ValueT>()))>
but gets substitution error. However, nothing prevents to deduce the second template parameter void in another way and the compilers takes the first declaration.
P.S. I know that this explanation is not good but it may help dummies like me!
It is a bit strange. I am trying to write a code which detects whether a type is a string object. The char * or other object types should lead to false not true. The following code gives me:
error: template parameters not deducible in partial specialization:
I do not understand what the message means. Even searched online, I cannot figure out how to fix this:
#include <iostream>
#include <type_traits>
#include <string>
template <typename T>
struct is_string : std::false_type { };
template <typename T>
struct is_string<std::string> : std::true_type<T> { };
class Temp
{
int a;
};
int main()
{
// Expect: false
std::cout << is_string<int>::value << std::endl;
std::cout << is_string<char *>::value << std::endl;
std::cout << is_string<Temp>::value << std::endl;
// Expect: true
std::cout << is_string<std::string>::value << std::endl;
std::cout << is_string<const std::string>::value << std::endl;
std::cout << is_string<std::string&>::value << std::endl;
}
If there is a out of box std tool, that's welcome
The template specialization should look like this:
template <>
struct is_string<std::string> : std::true_type { };
But even if you use it, your template will return false for cv-qualified string or references to it.
I did not use std::is_same because it rejects types such as const string or string & as string.
The right way to do it is std::is_same_v<std::remove_cv_t<std::remove_reference_t<your_type>>, std::string> (or using std::decay_t, as the other answer suggests).
Or, in C++20: std::is_same_v<std::remove_cvref_t<<your_type>, std::string>.
"Typo" in specialization syntax, it should be:
template <>
struct is_string<string> : std::true_type {};
To handle const std::string or std::string&, you might compose with std::decay
template <typename T>
using is_string_like = is_string<std::decay_t<T>>;
or using std::is_same:
template <typename T>
using is_string_like = std::is_same<std::string, std::decay_t<T>>;
When you create a specialization, you can kinda think of the template parameters of the specialization as "variables" for the specialization. You can give as many as you want, but they must be used in the angle brackets that are part of the class.
When you create a full specialization, you do not provide any template parameters - empty angle brackets.
The other answers by HolyBlackCat and Jarod42 are correct, in that they get rid of your compiler error, and provide a working implementation for your vague specifications.
However, both of them suggest doing more than removing cv-qualifiers from the type. I do not recommend doing this, as it goes against how the type traits in the standard library work and it could be quite surprising to users.
Since you actually want that as a result, I would suggest providing a separate metafunction that uses the basic one, and make it clear that it is removing the reference.
static_assert(std::is_null_pointer<std::nullptr_t>::value);
static_assert(std::is_null_pointer<std::nullptr_t const volatile>::value);
static_assert(not std::is_null_pointer<std::nullptr_t &>::value);
static_assert(std::is_unsigned<unsigned long>::value);
static_assert(std::is_unsigned<unsigned long const volatile>::value);
static_assert(not std::is_unsigned<unsigned long &>::value);
Plus, std::string is just one of the possible standard string types. What about a string using a different character type (e.g., std::wstring), or the same character type but a different allocator (e.g., std::pmr::string)?
Those already show how to limit your trait for just std::string. Here is one way to do it for any standard string type.
namespace detail {
template <typename T>
struct is_string
: std::false_type
{};
// Partial specialization - parameters used to qualify the specialization
template <typename CharT, typename TraitsT, typename AllocT>
struct is_string<std::basic_string<CharT, TraitsT, AllocT>>
: std::true_type
{};
}
template <typename T>
using is_basic_string = detail::is_string<std::remove_cv_t<T>>;
static_assert(is_basic_string<std::string>::value);
static_assert(is_basic_string<std::string const volatile>::value);
static_assert(not is_basic_string<std::string &>::value);
static_assert(is_basic_string<std::wstring>::value);
static_assert(is_basic_string<std::pmr::string>::value);
The specialization should not include typename T, so:
template <>
struct is_string<std::string> : std::true_type<T> { };
Since you want const std::string and std::string& and combinations thereof to count as string too, you may find it easier to inherit from integral_constant instead (which is what false_type and true_type does).
Example:
#include <iostream>
#include <string>
#include <type_traits>
template <typename T>
struct is_string : std::integral_constant<bool,
std::is_same_v<std::string, std::decay_t<T>>> {};
// helper
template<typename T>
inline constexpr bool is_string_v = is_string<T>::value;
I would like to design a template that automatically provides an operator<<(std::ostream&, const T&) for all classes T for which T::print_to(std::ostream&) exists and can be called, so that I can define the printing function as a member function (and, in particular, take advantage of virtual calls).
Through trial and error, I managed to arrive at this:
template<typename T, typename = decltype(std::declval<T>().print_to(std::declval<std::ostream&>()))>
std::ostream &operator<<(std::ostream &s, const T &t) {
t.print_to(s);
return s;
}
It seems to be working, but since I am still new to SFINAE and this kind of tricks, would like to know if there is any pitfall or enhancement that can be made. I put a small test bench at https://ideone.com/uLJxac.
If possible, I would like to have a C++14 solution, because I am working with a C++14 code base. However, if using C++17 allows for a better solution, than I am also interested to that one.
It seems to me that your applying SFINAE correctly in your operator<<(); I don't see pitfalls in your solution.
I propose another version (C++11 compatible, so also C++14) just because require less typewriting
template <typename T>
auto operator<< (std::ostream & s, T const & t)
-> decltype( t.print_to(s), s )
{
t.print_to(s);
return s;
}
Edit:
There is no pitfall with your code, sorry about that. But this answer enables you to write code more like C++20 concept:
template <class T>
auto& operator << (std::ostream &out, const printable_t<T> &t)
{
t.print_to(out);
return out;
}
In fact, I wrote a C++17 concept_check library based on detector and can be used in this way.
For more info on concept support in C++20, have a look at these 2:Constraints and concepts (since c++20) and Constraints and concepts (TS)
Original answer:
std::experiment::is_detector can do the magic for you. Though it is not in standard library, it is not difficult to implement and that link gives the suggested implementation.
Here I will give you how to detect that function along with my implementaion of is_detected_v.
#include <type_traits>
#include <utility>
#include <ostream>
// For support for C++17 is not complete in many compiler, I also define void_t
template <class...> using void_t = void;
namespace impl {
template <class Default, class AlwaysVoid, template <class...> class Op, class ...Args>
struct detector: private std::false_type
{
using std::false_type::value;
using type = Default;
};
template <class Default, template <class...> class Op, class ...Args>
struct detector<Default, void_t<Op<Args...>>, Op, Args...>: private std::true_type
{
using std::true_type::value;
using type = Op<Args...>;
};
} // namespace impl
struct nonsuch {};
#define CONCEPT_T constexpr const static inline bool
template <template<class...> class Op, class ...Args>
CONCEPT_T is_detected_v = impl::detector<nonsuch, void, Op, Args...>::value;
// Detect whether print_to exists.
template <class T>
using print_to_ret_t = decltype( std::declval<T>().print_to( std::declval<std::ostream&>() ) );
template <class T>
CONCEPT_T has_func_print_to_v = is_detected_v<print_to_ret_t, T>;
template <class T, std::enable_if_t< has_func_print_to_v<T> >>
using printable_t = T;
#undef CONCEPT_T
You can try to add C++14 support to this code. It won't be too difficult. The CONCEPT_Tmust be changed to constexpr const static bool to adjust to C++14.
I have the following traits class(IsLexCastable) to check if a type can be converted to a string by calling boost::lexical_cast<string>. It erroneously returns true for vector<int>.
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <boost/lexical_cast.hpp>
using namespace std;
using namespace boost;
namespace std
{
/// Adding to std since these are going to be part of it in C++14.
template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
}
template <typename T, typename = void>
struct IsLexCastable : std::false_type
{
};
template <typename T>
struct IsLexCastable<T, std::enable_if_t<std::is_same<std::string, decltype(boost::lexical_cast<std::string>(std::declval<T>()))>::value> > : std::true_type
{
};
int main()
{
vector<int> a = {1, 2, 3};
// cout << lexical_cast<string>(a) << endl;
cout << IsLexCastable<decltype(a)>::value << endl;
return 0;
}
This program prints 1, but lexical_cast<string>(a) results in a compile error. What is the right way to implement IsLexCastable?
(This was compiled with g++48 -std=c++11, and boost 1.55.0.)
Your expression is not sufficient, as the lexical_cast function template takes everything and only reports errors via an internal static_assert. Instead test whether inserting the object into an std::ostream is valid:
template <typename T, typename=void>
struct IsLexCastable : std::false_type {};
// Can be extended to consider std::wostream as well for completeness
template <typename T>
struct IsLexCastable<T,
decltype(void(std::declval<std::ostream&>() << std::declval<T>()))>
: std::true_type {};
Demo.
That requirement is called OutputStreamable by the documentation, and the direct one imposed onto the source type.
Why did your implementation not work?
decltype only causes the instantiation of the declaration of a function template. The internal static assertion is triggered inside the definition of lexical_cast though, hence it cannot be used in SFINAE.
[temp.inst]/10:
If a function template or a member function template specialization
is used in a way that involves overload resolution, a declaration of the specialization is implicitly instantiated (14.8.3).
The answer by Columbo answers the question, but I had problems adapting to to an input stream. My use case was that I wanted to call lexical_cast to convert from string to T. Boost offers has_right_shift and has_left_shift, which worked and should allow more flexibility for similar constructs.
template <typename T, typename=void>
struct IsLexCastable : std::false_type {};
template <typename T>
struct IsLexCastable<T,
typename std::enable_if<boost::has_right_shift<T>::value>::type>
: std::true_type {};
I have an SFINAE problem:
In the following code, I want the C++ compiler to pick the specialized functor and print "special", but it's printing "general" instead.
#include <iostream>
#include <vector>
template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename T::Vec> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
How can I fix it so that the specialized struct is used automatically? Note I don't want to directly specialize the Functor struct on Foo, but I want to specialize it on all types that have a Vec type.
P.S.: I am using g++ 4.4.4
Sorry for misleading you in the last answer, I thought for a moment that it would be simpler. So I will try to provide a complete solution here. The general approach to solve this type of problems is to write a traits helper template and use it together with enable_if (either C++11, boost or manual implementation) to decide a class specialization:
Trait
A simple approach, not necessarily the best, but simple to write would be:
template <typename T>
struct has_nested_Vec {
typedef char yes;
typedef char (&no)[2];
template <typename U>
static yes test( typename U::Vec* p );
template <typename U>
static no test( ... );
static const bool value = sizeof( test<T>(0) ) == sizeof(yes);
};
The approach is simple, provide two template functions, that return types of different sizes. One of which takes the nested Vec type and the other takes ellipsis. For all those types that have a nested Vec the first overload is a better match (ellipsis is the worst match for any type). For those types that don't have a nested Vec SFINAE will discard that overload and the only option left will be the ellipsis. So now we have a trait to ask whether any type has a nested Vec type.
Enable if
You can use this from any library, or you can roll your own, it is quite simple:
template <bool state, typename T = void>
struct enable_if {};
template <typename T>
struct enable_if<true,T> {
typedef T type;
};
When the first argument is false, the base template is the only option, and that does not have a nested type, if the condition is true, then enable_if has a nested type that we can use with SFINAE.
Implementation
Now we need to provide the template and the specialization that will use SFINAE for only those types with a nested Vec:
template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename enable_if<has_nested_Vec<T>::value>::type > {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
Whenever we instantiate Functor with a type, the compiler will try to use the specialization, which will in turn instantiate has_nested_Vec and obtain a truth value, passed to enable_if. For those types for which the value is false, enable_if does not have a nested type type, so the specialization will be discarded in SFINAE and the base template will be used.
Your particular case
In your particular case, where it seems that you don't really need to specialize the whole type but just the operator, you can mix the three elements into a single one: a Functor that dispatches to one of two internal templated functions based on the presence of Vec, removing the need for enable_if and the traits class:
template <typename T>
class Functor {
template <typename U>
void op_impl( typename U::Vec* p ) const {
std::cout << "specialized";
}
template <typename U>
void op_impl( ... ) const {
std::cout << "general";
}
public:
void operator()() const {
op_impl<T>(0);
}
};
Even though this is an old question, I think it's still worth providing a couple more alternatives for quickly fixing the original code.
Basically, the problem is not with the use of SFINAE (that part is fine, actually), but with the matching of the default parameter in the primary template (void) to the argument supplied in the partial specialization(typename T::Vec). Because of the default parameter in the primary template, Functor<Foo> actually means Functor<Foo, void>. When the compiler tries to instantiate that using the specialization, it tries to match the two arguments with the ones in the specialization and fails, as void cannot be substituted for std::vector<int>. It then falls back to instantiating using the primary template.
So, the quickest fix, which assumes all your Vecs are std::vector<int>s, is to replace the line
template<class T, class V = void>
with this
template<class T, class E = std::vector<int>>
The specialization will now be used, because the arguments will match. Simple, but too limiting. Clearly, we need to better control the type of the argument in the specialization, in order to make it match something that we can specify as the default parameter in the primary template. One quick solution that doesn't require defining new traits is this:
#include <iostream>
#include <vector>
#include <type_traits>
template<class T, class E = std::true_type>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
template<class T>
struct Functor<T, typename std::is_reference<typename T::Vec&>::type> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
This will work for any Vec type that could make sense here, including fundamental types and arrays, for example, and references or pointers to them.
Another alternative for detecting the existence of a member type is to use void_t. As valid partial specialisations are preferable to the general implementation as long as they match the default parameter(s), we want a type that evaluates to void when valid, and is only valid when the specified member exists; this type is commonly (and, as of C++17, canonically) known as void_t.
template<class...>
using void_t = void;
If your compiler doesn't properly support it (in early C++14 compilers, unused parameters in alias templates weren't guaranteed to ensure SFINAE, breaking the above void_t), a workaround is available.
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
As of C++17, void_t is available in the utilities library, in type_traits.
#include <iostream>
#include <vector>
#include <type_traits> // For void_t.
template<class T, class V = void>
struct Functor {
void operator()() const {
std::cerr << "general" << std::endl;
}
};
// Use void_t here.
template<class T>
struct Functor<T, std::void_t<typename T::Vec>> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
struct Foo {
typedef std::vector<int> Vec;
};
int main() {
Functor<Foo> ac;
ac();
}
With this, the output is special, as intended.
In this case, since we're checking for the existence of a member type, the process is very simple; it can be done without expression SFINAE or the type_traits library, allowing us to rewrite the check to use C++03 facilities if necessary.
// void_t:
// Place above Functor's definition.
template<typename T> struct void_t { typedef void type; };
// ...
template<class T>
struct Functor<T, typename void_t<typename T::Vec>::type> {
void operator()() const {
std::cerr << "special" << std::endl;
}
};
To my knowledge, this should work on most, if not all, SFINAE-capable C++03-, C++11-, C++14-, or C++1z-compliant compilers. This can be useful when dealing with compilers that lag behind the standard a bit, or when compiling for platforms that don't have C++11-compatible compilers yet.
For more information on void_t, see cppreference.