template specialization and default template parameters and sfinae - c++

I have put together an example on creating a base template with a number of specializations.
#include <iostream>
template<typename T, typename U = void>
struct foo {
static void apply() {
std::cout << "a: " << __FUNCTION__ << std::endl;
}
};
template<typename U>
struct foo<int, U> {
static void apply() {
std::cout << "b: " << __FUNCTION__ << std::endl;
}
};
template<typename U>
struct foo<double, U> {
static void apply() {
std::cout << "c: " << __FUNCTION__ << std::endl;
}
};
template<>
struct foo<double, double> {
static void apply() {
std::cout << "d: " << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct foo<T, std::enable_if_t<std::is_same_v<T, char>>> {
static void apply() {
std::cout << "e: " << __FUNCTION__ << std::endl;
}
};
template<>
struct foo<short> {
static void apply() {
std::cout << "f: " << __FUNCTION__ << std::endl;
}
};
template<>
struct foo<unsigned long, void> {
static void apply() {
std::cout << "g: " << __FUNCTION__ << std::endl;
}
};
int main() {
foo<long>::apply();
foo<long, long>::apply();
foo<int>::apply();
foo<int, int>::apply();
foo<double>::apply();
foo<double, float>::apply();
foo<double, double>::apply();
foo<char>::apply();
foo<short>::apply();
foo<unsigned long>::apply();
return 0;
}
When a specialization is defined and a template parameter is defined in the base template such as the template parameter U which is defaulted to void how is this propagated to the specializations. Is the done at the point of specialization as in the first specialization foo<int, U> and U must be void as it is unspecified and adopted from the base template?
Also with the
template<typename T>
struct foo<T, std::enable_if_t<std::is_same_v<T, char>>>
specialization, the enable_if_t yields the type void, why does this not collide with the base template and how is it considered more specialized?
Any additional quotes from the standard to complement answers are additionally welcome.

Related

Single C++ Partial Specialization for const, non-const, volatile

In the example below I can effectively strip the const, volatile and reference qualifiers and use the single specialization for shared pointers. This is solved by the adding one more level of abstraction. How could I solve this without doing so? I could I just use the specialisations and match on shared_pointer, shared_pointer const etc?
#include <iostream>
#include <type_traits>
namespace detail {
template<typename T>
struct display;
template<typename T>
struct display<std::shared_ptr<T>> {
static void apply() {
std::cout << __FUNCTION__ << std::endl;
}
};
}
template<typename T>
void display() {
detail::display<std::remove_cvref_t<T>>::apply();
}
int main() {
std::shared_ptr<int> t;
display<decltype(t)>();
return 0;
}
So I have come up with a solution which I like much better which I thought I would share.
template<typename T>
struct is_shared_pointer : std::false_type { };
template<template<typename > typename T, typename U>
struct is_shared_pointer<T<U>> : std::is_same<std::decay_t<T<U>>, std::shared_ptr<U>> {};
template<typename T, typename Enable = void>
struct display;
template<typename T>
struct display<T, std::enable_if_t<is_shared_pointer<T>::value>> {
static void apply() {
std::cout << "shared ptr: " << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_integral_v<T>>> {
static void apply() {
std::cout << "integral :" << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_void_v<T>>> {
static void apply() {
std::cout << "void: " << __FUNCTION__ << std::endl;
}
};
template<typename T>
struct display<T, std::enable_if_t<std::is_floating_point_v<T>>> {
static void apply() {
std::cout << "floating: " << __FUNCTION__ << std::endl;
}
};
int main() {
std::shared_ptr<int> t;
display<decltype(t)>();
return 0;
}
That being said, I am open to suggestions, ideas and techniques.

Best way to specialize function using enable_if for only some types

I have this code that specializes the print function for two types, and reverts back to the base version for any other types.
My question is, is there a way to write this without having to type out the negative case for all the specialized versions in the enable_if of the base print function?
i.e. is there a way to remove all the !std::is_same and still have an unambiguous print function?
Any versions of C++ welcome, but one that works in c++14 would be helpful.
#include <iostream>
template<typename T, std::enable_if_t<!std::is_same<T, int>::value && !std::is_same<T, double>::value, int> = 42>
void print(T data)
{
std::cout << "base:" << data << std::endl;
}
template<typename T, std::enable_if_t<std::is_same<T, double>::value, int> = 42>
void print(T data)
{
std::cout << "double:" << data << std::endl;
}
template<typename T, std::enable_if_t<std::is_same<T, int>::value, int> = 42>
void print(T data)
{
std::cout << "int:" << data << std::endl;
}
int main()
{
std::string foo("foo");
double bar = 1.2;
int baz = 5;
print(foo);
print(bar);
print(baz);
}
For your use case, you can simply provide overloads for the print function as needed
#include <iostream>
template<typename T>
void print(T data)
{
std::cout << "base:" << data << std::endl;
}
void print(double data)
{
std::cout << "double:" << data << std::endl;
}
void print(int data)
{
std::cout << "int:" << data << std::endl;
}
However, if you have more complicated constraints on T, then you can't specialize print without explicitly providing the negation of the constraints in the "default" case.
If you have access to c++17, you can write this in a single function. The usual if-else logic means that the base case is only triggered if the "specializations" are not. This avoids having to specify the negations.
template<typename T>
void print(T data)
{
if constexpr(std::is_same<T, double>{})
std::cout << "double:" << data << std::endl;
else if constexpr(std::is_same<T, int>{})
std::cout << "int:" << data << std::endl;
else // if not same as int or double
std::cout << "base:" << data << std::endl;
}
One way to avoid complementary conditions is to give an overload priority between overload:
template <std::size_t N> struct priority_overload : priority_overload<N - 1> {};
template <> struct priority_overload<0> {}; // Least priority
and then
template<typename T>
void print(T data, priority_overload<0>) // fallback
{
std::cout << "base:" << data << std::endl;
}
template<typename T, std::enable_if_t<condition1<T>::value, int> = 42>
void print(T data, priority_overload<1>)
{
std::cout << "cond1:" << data << std::endl;
}
template<typename T, std::enable_if_t<condition2<T>::value, int> = 42>
void print(T data, priority_overload<2>)
{
std::cout << "cond2:" << data << std::endl;
}
template<typename T>
void print(T data)
{
print(data, priority_overload<10>{});
// Priority should be greater or equal to the one of possible overloads
}

SFINAE matches don't go as expected

I am using both g++ 7.5.0 and clang 6.0.0 on ubuntu to try the SFINAE function of auto dispatching function call according to the method existence of an object and the result doesn't go as expected.
what I expected is that for the container of vector, it should invoke the clear method of the vector in the container's destruction function. for primitive types like int, it does nothing other than printing out messages.
but they give both the later one now. I wonder what's wrong here.
#include <iostream>
#include <typeinfo>
#include <vector>
using namespace std;
template <typename T> struct has_clear {
typedef char true_type;
typedef int false_type;
template <typename U, size_t (U::*)() const> struct SFINAE {
};
template <typename U> static char Test(SFINAE<U, &U::clear> *);
template <typename U> static int Test(...);
static const bool has_method = sizeof(Test<T>(nullptr) == sizeof(char));
typedef decltype(Test<T>(nullptr)) ret_type;
// typedef Test<T>(0) type_t;
};
template <typename T> class MyContainer {
// using typename has_clear<T>::true_type;
// using typename has_clear<T>::false_type;
T _obj;
public:
MyContainer(const T &obj) : _obj(obj) {}
// static void clear(MyContainer *m);
void clear(const typename has_clear<T>::true_type t)
{
cout << "the " << typeid(_obj).name() << " object has clear() function!" << endl;
cout << "typeid(t).name(): " << typeid(t).name() << endl;
_obj.clear();
cout << "clear has be done!" << endl;
}
void clear(const typename has_clear<T>::false_type t)
{
cout << "the " << typeid(_obj).name() << " object has no clear() function!" << endl;
cout << "typeid(t).name(): " << typeid(t).name() << endl;
cout << "just do nothing and quit!" << endl;
}
~MyContainer()
{
cout << "has_clear<T>::true_type: " << typeid(typename has_clear<T>::true_type()).name()
<< endl;
cout << "has_clear<T>::flase_type: " << typeid(typename has_clear<T>::false_type()).name()
<< endl;
clear(typename has_clear<T>::ret_type());
};
// template <bool b> ~MyContainer();
};
int main()
{
cout << "before MyContainer<vector<int>>" << endl;
{
vector<int> int_vec;
MyContainer<vector<int>> int_vec_container(int_vec);
}
cout << "after MyContainer<vector<int>>" << endl;
cout << "before MyContainer<int>" << endl;
{
MyContainer<int> int_container(1);
}
cout << "after MyContainer<int>" << endl;
}
it yields:
before MyContainer<vector<int>>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the St6vectorIiSaIiEE object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<vector<int>>
before MyContainer<int>
has_clear<T>::true_type: FcvE
has_clear<T>::flase_type: FivE
the i object has no clear() function!
typeid(t).name(): i
just do nothing and quit!
after MyContainer<int>
You have a bug in the implementation of has_clear:
template <typename U, size_t (U::*)() const> struct SFINAE {
}; // ^^^^^^^^^^^^^^^^^^^^^
std::vector::clear returns void and can't be const. So:
template <typename U, void (U::*)()> struct SFINAE {
};
I don't know what the issue is with your implementation has_clear, but it can be replaced with this greatly simplified, working implementation using more modern SFINAE/type_traits features:
template<typename T, typename Enable = void>
struct has_clear : std::false_type {};
template<typename T>
struct has_clear<
T,
std::enable_if_t<
std::is_same_v<decltype(&T::clear), void (T::*)()> ||
std::is_same_v<decltype(&T::clear), void (T::*)() noexcept>
>
> : std::true_type {};
And for convenience:
template<typename T>
constexpr bool has_clear_v = has_clear<T>::value;
Combined with if constexpr, you can very cleanly and simply decide which code path to run when others would fail to compile. For example:
template<typename T>
void maybe_clear(T t){
if constexpr (has_clear_v<T>){
// only compiled when T has a non-static clear() method
std::cout << "clearing " << typeid(T).name() << '\n';
t.clear();
} else {
// only compiled when T does not have a non-static clear() method
std::cout << "doing nothing with " << typeid(T).name() << '\n';
}
}
I believe this achieves what you want, but correct if I have misunderstood. This solution comes at the cost of requiring C++17.
Live Demo

Specialized templates for pointer and c-style array variables

I have the following code which works, when I compile the code with C++11 enabled. Is it also possible to write the specializations such that it will work with a C++98 compiler?
#include <iostream>
#include <type_traits>
#include <cstdint>
template<typename T, typename std::enable_if_t<!std::is_pointer<T>::value, int> = 0>
void CheckSize(T const)
{
cout << "sizeof(data): " << sizeof(T) << endl;
}
template<typename T, typename std::enable_if_t<std::is_pointer<T>::value, int> = 0>
void CheckSize(T const)
{
cout << "sizeof(data) (pointer): " << sizeof(std::remove_pointer<T>) << endl;
}
template<typename T, size_t N>
void CheckSize(T const (&)[N])
{
cout << "sizeof(data) (array): " << sizeof(T) * N << endl;
}
int main()
{
uint8_t bufferNumber{0};
CheckSize(bufferNumber);
uint8_t bufferArray[] = {1,2,3,4,5,6};
CheckSize(bufferArray);
uint8_t *bufferPointer{nullptr};
CheckSize(bufferPointer);
return 0;
}
I also don't understand why the compiler can't apply the specialization when writing:
template<typename T>
void CheckSize(T const)
{
cout << "sizeof(data): " << sizeof(T) << endl;
}
template<typename T>
void CheckSize(T const*)
{
cout << "sizeof(data) (pointer): " << sizeof(T) << endl;
}
MSVC2015 will print an error message that the function call is ambigious for the overloaded function for the bufferArray variable and MinGW will use the CheckSize(T const) function for the bufferPointer variable.
As mentioned in the comments, enable_if and the type traits you're using are implementable using C++98. Boost provides implementations, and I would recommend using them if you're already using boost, but they're fairly simple to implement if you're not using boost:
template <bool b, typename T>
struct enable_if;
template <typename T>
struct enable_if<true, T>
{
typedef T type;
};
template <typename T>
struct is_pointer
{
const static bool value = false;
};
template <typename T>
struct is_pointer<T*>
{
const static bool value = true;
};
template <typename T>
struct remove_pointer
{
typedef T type;
};
template <typename T>
struct remove_pointer<T*>
{
typedef T type;
};
template<typename T>
typename enable_if<!is_pointer<T>::value, void>::type
CheckSize(T const)
{
std::cout << "sizeof(data): " << sizeof(T) << std::endl;
}
template<typename T>
typename enable_if<is_pointer<T>::value, void>::type
CheckSize(T const)
{
std::cout << "sizeof(data) (pointer): " << sizeof(typename remove_pointer<T>::type) << std::endl;
}
template<typename T, size_t N>
void CheckSize(T const (&)[N])
{
std::cout << "sizeof(data) (array): " << sizeof(T) * N << std::endl;
}
Live Demo
Alternatively, you could use partial specialization rather than SFINAE to select your overload. Since functions can't be partially specialized, you can partially specialize a helper class:
template<typename T>
struct CheckSizeHelper
{
static void size() {
std::cout << "sizeof(data): " << sizeof(T) << std::endl;
}
};
template<typename T>
struct CheckSizeHelper<T*>
{
static void size() {
std::cout << "sizeof(data) (pointer): " << sizeof(T) << std::endl;
}
};
template<typename T, size_t N>
struct CheckSizeHelper<T[N]>
{
static void size() {
std::cout << "sizeof(data) (array): " << sizeof(T) * N << std::endl;
}
};
template<typename T>
void CheckSize(T const&) {
CheckSizeHelper<T>::size();
}
Live Demo
As it was mentioned in the comments, one option is to use the boost::enable_if
Another option is to use partial template specialization for classes instead of function overloading:
#include <iostream>
#include <type_traits>
#include <cstdint>
using namespace std;
template<typename T>
struct SizeChecker
{
static void CheckSize(T const)
{
cout << "sizeof(data): " << sizeof(T) << endl;
}
};
template<typename T>
struct SizeChecker<T*>
{
static void CheckSize(T* const)
{
cout << "sizeof(data) (pointer): " << sizeof(T*) << endl;
}
};
template<typename T, size_t N>
struct SizeChecker<T[N]>
{
static void CheckSize(const T(&)[N])
{
cout << "sizeof(data) (array): " << sizeof(T) * N << endl;
}
};
template <typename T>
void CheckSize(const T& val)
{
SizeChecker<T>::CheckSize(val);
}
int main()
{
char bufferNumber{0};
CheckSize(bufferNumber);
char bufferArray[] = {1,2,3,4,5,6};
CheckSize(bufferArray);
char *bufferPointer{NULL};
CheckSize(bufferPointer);
return 0;
}

Partial template function specialization with enable_if: make default implementation

Using C++11's enable_if I want to define several specialized implementations for a function (based on the type of the parameter, say) as well as a default implementation. What is the correct way to define it?
The following example does not work as intended since the "generic" implementation is called, whatever the type T.
#include <iostream>
template<typename T, typename Enable = void>
void dummy(T t)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type>
void dummy(T t)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type>
void dummy(T t)
{
std::cout << "Floating point: " << t << std::endl;
}
int main() {
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Generic: 5"
}
One solution in my minimal example consists in explicitly declaring the "generic" implementation as not for integral nor floating point types, using
std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value>::type
This is exactly what I want to avoid, since in my real use cases there are a lot of specialized implementations and I would like to avoid a very long (error prone!) condition for the default implementation.
You can introduce a rank to give priority to some of your overloads:
template <unsigned int N>
struct rank : rank<N - 1> { };
template <>
struct rank<0> { };
You can then define your dummy overloads like this:
template<typename T>
void dummy(T t, rank<0>)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T,
typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy(T t, rank<1>)
{
std::cout << "Floating point: " << t << std::endl;
}
Then, you can hide the call behind a dispatch:
template <typename T>
void dispatch(T t)
{
return dummy(t, rank<1>{});
}
Usage:
int main()
{
dispatch(5); // Print "Integral: 5"
dispatch(5.); // Print "Floating point: 5"
dispatch("hi"); // Print "Generic: hi"
}
live example on wandbox
Explanation:
Using rank introduces "priority" because implicit conversions are required to convert a rank<X> to a rank<Y> when X > Y. dispatch first tries to call dummy with rank<1>, giving priority to your constrained overloads. If enable_if fails, rank<1> is implicitly converted to rank<0> and enters the "fallback" case.
Bonus: here's a C++17 implementation using if constexpr(...).
template<typename T>
void dummy(T t)
{
if constexpr(std::is_integral_v<T>)
{
std::cout << "Integral: " << t << std::endl;
}
else if constexpr(std::is_floating_point_v<T>)
{
std::cout << "Floating point: " << t << std::endl;
}
else
{
std::cout << "Generic: " << t << std::endl;
}
}
live example on wandbox
Function cannot be partially specialized. I assume what you want to do is to prefer those overloads which contains explicit condition? One way to achieve that is by using variadic arguments ellipsis in declaration of the default function as the ellipsis function have lower priority in overload resolution order:
#include <iostream>
template<typename T>
void dummy_impl(T t, ...)
{
std::cout << "Generic: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Integral: " << t << std::endl;
}
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
void dummy_impl(T t, int)
{
std::cout << "Floating point: " << t << std::endl;
}
template <class T>
void dummy(T t) {
dummy_impl(t, int{});
}
int main() {
dummy(5);
dummy(5.);
dummy("abc");
}
Output:
Integral: 5
Floating point: 5
Generic: abc
[live demo]
Another option as #doublep mention in comment is by use of structure with implementation of your function and then partially specialize it.
I would use tag dispatching like so:
namespace Details
{
namespace SupportedTypes
{
struct Integral {};
struct FloatingPoint {};
struct Generic {};
};
template <typename T, typename = void>
struct GetSupportedType
{
typedef SupportedTypes::Generic Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_integral< T >::value >::type >
{
typedef SupportedTypes::Integral Type;
};
template <typename T>
struct GetSupportedType< T, typename std::enable_if< std::is_floating_point< T >::value >::type >
{
typedef SupportedTypes::FloatingPoint Type;
};
template <typename T>
void dummy(T t, SupportedTypes::Generic)
{
std::cout << "Generic: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::Integral)
{
std::cout << "Integral: " << t << std::endl;
}
template <typename T>
void dummy(T t, SupportedTypes::FloatingPoint)
{
std::cout << "Floating point: " << t << std::endl;
}
} // namespace Details
And then hide the boiler plate code like so:
template <typename T>
void dummy(T t)
{
typedef typename Details::GetSupportedType< T >::Type SupportedType;
Details::dummy(t, SupportedType());
}
GetSupportedType gives you one central way to guess the actual type you are going to use, that's the one you want to specialize everytime you add a new type.
Then you just invoke the right dummy overload by providing an instance of the right tag.
Finally, invoke dummy:
dummy(5); // Print "Generic: 5"
dummy(5.); // Print "Floating point: 5"
dummy("lol"); // Print "Generic: lol"