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.
Related
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.
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;
}
I tried to implement template function specialization. You can run my tiny code in this fiddle. You can also see it below
#include <iostream>
#include <vector>
#include <list>
template <typename T>
struct is_vector {
static const bool value = false;
};
template <typename T>
struct is_vector<std::vector<T>> {
static const bool value = true;
using type = std::vector<T>;
};
template <typename T>
struct is_list {
static const bool value = false;
};
template <typename T>
struct is_list<std::list<T>> {
static const bool value = true;
using type = std::list<T>;
};
template<typename T, class = typename std::enable_if<is_list<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
/*
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
*/
//The above code will cause an error, if we uncomment it
int main()
{
foo(std::list<int>{});
return 0;
}
In this code, I have several lines commented:
template<typename T, class = typename std::enable_if<is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
If I uncomment it, I get "redifinition" error. I'm not sure how to fix it.
The reason is quite simple: default values for template type arguments are not a part of a function signature. It means that you have the same template defined two times.
You might move SFINAE part in to the function return type, as it is suggested by other answers, or change the code to:
template<typename T, std::enable_if_t<is_list<T>::value, int> = 0>
void foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T, std::enable_if_t<is_vector<T>::value, int> = 1>
void foo(T t) {
std::cout << "is vector" << std::endl;
}
You can do this instead.
template<typename T>
typename std::enable_if<is_list<T>::value>::type foo(T t) {
std::cout << "is list" << std::endl;
}
template<typename T>
typename std::enable_if<is_vector<T>::value>::type foo(T t) {
std::cout << "is vector" << std::endl;
}
Not sure if this is what you are after, but you could just check if either list or vector is a matching type:
template<typename T, class = typename std::enable_if<is_list<T>::value || is_vector<T>::value>::type>
void foo(T t) {
std::cout << "is list" << std::endl;
}
Updated fiddle: https://godbolt.org/g/oD3o9q
Update (for C++14):
template<typename T, class = std::enable_if_t<is_list<T>::value || is_vector<T>::value>>
void foo(T t) {
std::cout << "is list" << std::endl;
}
Lets say we have some variadic template and need to treat std::reference_wrapper parameters differently.
How can we achieve that?
You can make a trait to tell if a type is reference_wrapper
template<typename T>
struct is_reference_wrapper : false_type {};
template<typename T>
struct is_reference_wrapper<reference_wrapper<T>> : true_type{};
Then you can use it to disambiguate:
template<typename T>
void do_stuff(T&& t, false_type)
{
cout << "Normal: " << t << endl;
}
template<typename T>
void do_stuff(T&& ref, true_type)
{
cout << "Ref: " << ref.get() << endl;
}
template<typename... Ts>
void foo(Ts&&... ts)
{
[[maybe_unused]] int arr[] = {
(do_stuff(forward<Ts>(ts), is_reference_wrapper<decay_t<Ts>>{}), 0)...
};
}
demo
I want to use SFINAE to enable a particular template if the user passes a function pointer as a parameter.
I have googled around but found nothing - I also tried looking at the <type_traits> documentation but couldn't find anything that resembled a is_function_ptr<T>.
By function pointer, I mean global function pointers, like TReturn(*)(TArgs...).
Below is a type trait determining if something is a function pointer and a couple of test cases. Note, that to test if something is a function pointer, you need to test if std::is_pointer<P>::value is true and if std::is_function<T>::value is true where T is P with the pointer removed. The code below just does that:
#include <type_traits>
#include <iostream>
#include <utility>
template <typename Fun>
struct is_fun_ptr
: std::integral_constant<bool, std::is_pointer<Fun>::value
&& std::is_function<
typename std::remove_pointer<Fun>::type
>::value>
{
};
template <typename Fun>
typename std::enable_if<is_fun_ptr<Fun>::value>::type
test(Fun) {
std::cout << "is a function pointer\n";
}
template <typename Fun>
typename std::enable_if<!is_fun_ptr<Fun>::value>::type
test(Fun) {
std::cout << "is not a function pointer\n";
}
void f0() {}
void f1(int) {}
void f2(int, double) {}
struct s0 { void operator()() {} };
struct s1 { void operator()(int) {} };
struct s2 { void operator()(int, double) {} };
int main()
{
int v0(0);
int* p0(&v0);
void (*p1)() = &f0;
void (**p2)() = &p1;
std::cout << "v0="; test(v0);
std::cout << "p0="; test(p0);
std::cout << "p1="; test(p1);
std::cout << "p2="; test(p2);
std::cout << "f0="; test(&f0);
std::cout << "f1="; test(&f1);
std::cout << "f2="; test(&f2);
std::cout << "s0="; test(s0());
std::cout << "s1="; test(s1());
std::cout << "s2="; test(s2());
std::cout << "l0="; test([](){});
std::cout << "l1="; test([](int){});
std::cout << "l2="; test([](int, double){});
}
No SFINAE is needed to accept a function pointer or a member function pointer. To distinguish function objects from non-callable stuff SFINAE is needed, there's probably no way around this.
#include <utility>
#include <iostream>
template <typename Ret, typename... Parm>
void moo (Ret (*fp)(Parm...))
{
std::cout << "funptr" << std::endl;
}
template <typename Ret, typename Owner, typename... Parm>
void moo (Ret (Owner::*fp1)(Parm...))
{
std::cout << "memfunptr" << std::endl;
}
template <typename Funobj, typename... Parm,
typename Ret =
decltype((std::declval<Funobj>())
(std::forward(std::declval<Parm>())...))>
void moo (Funobj functor)
{
std::cout << "funobj" << std::endl;
}
void x1() {}
struct X2 { void x2() {} };
struct X3 { void operator()(){} };
int main()
{
moo(x1);
moo(&X2::x2);
moo(X3());
}