Uroc327
Uroc327

Reputation: 1429

pass nullptr to variadic template pointer

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

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Jarod42
Jarod42

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

Related Questions