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;
}
Related
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.
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
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 would like to discern between static arrays and pointers.
The following example fails to compile due to array-to-pointer conversions having exact match, making both foo's possible candidates.
Am I able to get the 2nd overload of foo to be unambiguously selected using type traits?
#include <iostream>
template<typename T>
void foo(const T* str)
{
std::cout << "ptr: " << str << std::endl;
}
template<typename T, size_t N>
void foo(const T (&str)[N])
{
std::cout << "arr: " << str << std::endl;
}
int main()
{
foo("hello world"); // I would like the array version to be selected
return 0;
}
You may use the following:
namespace detail
{
template <typename T> struct foo;
template <typename T>
struct foo<T*>
{
void operator()(const T* str) {std::cout << "ptr: " << str << std::endl;}
};
template <typename T, std::size_t N>
struct foo<T [N]>
{
void operator()(const T (&str)[N]) {std::cout << "arr: " << str << std::endl;}
};
}
template<typename T>
void foo(const T& t)
{
detail::template foo<T>()(t);
}
Live example
template<typename T>
typename std::enable_if<std::is_pointer<T>::value,void>::type
foo(const T str)
{
std::cout << "ptr: " << str << std::endl;
}
template<typename T, size_t N>
void
foo(const T (&str)[N])
{
std::cout << "arr: " << str << std::endl;
}