Reputation: 1429
I currently have the following functions:
template <typename T, typename... Args> void Get(T* out, Args*... other);
template <typename T> void Get(T* out);
template <> void Get<int>(int* out);
template <> void Get<int64>(int64* out);
template <> void Get<double>(double* out);
template <> void Get<char*>(char** out);
template <> void Get<void*>(void** out);
called using:
Get(&i, &t, &f);
with having i
as int
, t
as char*
and f
as double
.
This works great with one exception, if I want to pass a null-pointer.
Get(&i, nullptr, nullptr, &t, &f);
gives
main.cpp: In function ‘int main()’:
main.cpp:94:39: error: no matching function for call to ‘Get(int*, std::nullptr_t, std::nullptr_t, char**, float*)’
Get(&i, nullptr, nullptr, &txt, &f);
^
main.cpp:94:39: note: candidates are:
main.cpp:18:46: note: template<class T, class ... Args> void Get(T*, Args* ...)
template <typename T, typename... Args> void Get(T* out, Args*... other)
^
main.cpp:18:46: note: template argument deduction/substitution failed:
main.cpp:94:39: note: mismatched types ‘Args*’ and ‘std::nullptr_t’
Get(&i, nullptr, nullptr, &txt, &f);
^
main.cpp:28:28: note: template<class T> void Get(T*)
template <typename T> void Get(T* out)
^
main.cpp:28:28: note: template argument deduction/substitution failed:
main.cpp:94:39: note: candidate expects 1 argument, 5 provided
Get(&i, nullptr, nullptr, &txt, &f);
^
How do I have to rewrite my Get
functions to keep the old usage, except that they will accept a nullptr
too?
Upvotes: 4
Views: 1141
Reputation: 275385
First, some metaprogramming boilerplate. This defines enable_if_t
, all_of
:
template<bool B, class T=void>
using enable_if_t=typename std::enable_if<B,T>::type;
namespace details {
template <template<class>class test, class=void, class... Ts>
struct all_of : std::true_type {};
template <template<class>class test, class T0, class... Ts>
struct all_of<test, enable_if_t< !test<T0>::value >, Ts... > : std::false_type {};
template <template<class>class test, class T0, class... Ts>
struct all_of<test, enable_if_t< test<T0>::value >, Ts... > : all_of<test, void, Ts...> {};
}
template<template<class>class test, class... Ts>
struct all_of : details::all_of<test, void, Ts...> {};
both of which are generally useful. enable_if_t
is a C++11 thing in std
, if you have full support, just use std::enable_if_t
instead.
Next, a trait that detects if something is a pointer or nullptr:
template<class X>
struct is_ptr : std::false_type {};
template<class X>
struct is_ptr<X*> : std::true_type {};
template<>
struct is_ptr<std::nullptr_t> : std::true_type {};
We then use that trait to provide SFINAE protection to our Get
multi-arg method:
template <typename T, typename... Args>
enable_if_t<all_of<is_ptr, T, Args...>> Get(T const& out, Args const&... other);
Next, the interesting thing about function template specializations is "you aren't doing it" and "you shouldn't do it". You are actually overloading Get<T,Args...>
, you are just specializing Get<T>
. And there is little point if you are using deduction:
void Get( std::nullptr_t out );
void Get( int* out );
void Get( int64* out );
void Get( double* out );
void Get( char** out );
void Get( void** out );
put these single-arg versions of Get
before your variardic body if the variardic body does recursion.
It is rarely a good idea to specialize template functions -- just override.
Upvotes: 0
Reputation: 217275
You may do something like:
template <typename T, typename... Args>
typename std::enable_if<std::is_same<std::nullptr_t, T>::value || std::is_pointer<T>::value>::type
Get(T out, Args... other);
template <typename T>
typename std::enable_if<std::is_same<std::nullptr_t, T>::value || std::is_pointer<T>::value>::type
Get(T out);
So your specializations are different:
template <> void Get<int*>(int* out);
template <> void Get<int64*>(int64* out);
template <> void Get<double*>(double* out);
template <> void Get<char**>(char** out);
template <> void Get<void**>(void** out);
and potentially:
template <> void Get<nullptr_t>(nullptr_t); // the new one
BTW, you may prefer overloading (for Get
with one argument): Live example.
Upvotes: 2