Template argument as function unnamed argument - c++

This question continues Non-static data members class deduction
Here are the unnamed argument functions, that I'm using to return std::string representation of the data type
struct Boo {};
struct Foo {};
std::string class2str(const double) { return "Floating"; };
std::string class2str(const int) { return "Fixed Point"; };
std::string class2str(const Foo) { return "Class Foo"; };
std::string class2str(const Boo) { return "Class Boo"; };
int main(int argc, char* argv[])
{
int x_a;
double x_b;
Foo F;
Boo B;
std::cout << "x_a :" << class2str(x_a) << std::endl;
std::cout << "x_b :" << class2str(x_b) << std::endl;
std::cout << "Foo :" << class2str(F) << std::endl;
std::cout << "Boo :" << class2str(B) << std::endl;
};
For type deduction from the non-static member, I am using template:
struct Foo { double A = 33; }
template<typename Class, typename MemType>
std::string class2str(MemType Class::* mData)
{
return class2str(MemType{}); // Use of empty constructor
}
std::cout << "Foo::A :" << class2str(&Foo::A) << std::endl;
But this template requires the creation of an object with an empty constructor, which may simply not be there
struct Boo
{
double A;
Boo() = delete;
Boo(int x) :A(x) {};
};
struct Foo
{
double A = 33;
Boo b{ 0 };
};
// Compilation error: use of deleted function ‘Boo::Boo()’
std::cout << "Boo::b :" << class2str(&Foo::b) << std::endl;
How to implement this functionality, but without calling an empty constructor?
See online demo: https://onlinegdb.com/lpc5o8pUKy

(As I started writing the answer there was no answer to the question, but as I was about to post it I saw #Jarod42's answer which already show the tag dispatch approach. Posting this answer nonetheless as it uses a slightly different approach of full specializations of a deleted primary template, instead of non-template overloads)
You can use tag dispatch to delegate calls:
#include <iostream>
struct Boo {
double A;
Boo() = delete;
Boo(int x) : A(x){};
};
struct Foo {
double A = 33;
Boo b{0};
};
namespace detail {
template <typename T> struct Tag {};
template <typename T> std::string class2str_impl(Tag<T>) = delete;
template <> std::string class2str_impl(Tag<double>) { return "Floating"; };
template <> std::string class2str_impl(Tag<int>) { return "Fixed Point"; };
template <> std::string class2str_impl(Tag<Foo>) { return "Class Foo"; };
template <> std::string class2str_impl(Tag<Boo>) { return "Class Boo"; };
} // namespace detail
template <typename T> std::string class2str(T) {
return class2str_impl(detail::Tag<T>{});
}
template <typename Class, typename MemType>
std::string class2str(MemType Class::*) {
return class2str_impl(detail::Tag<MemType>{});
}
int main() {
int x_a{42};
double x_b{4.2};
Foo F{};
Boo B{x_a};
std::cout << "x_a :" << class2str(x_a) << std::endl;
std::cout << "x_b :" << class2str(x_b) << std::endl;
std::cout << "Foo :" << class2str(F) << std::endl;
std::cout << "Boo :" << class2str(B) << std::endl;
std::cout << "Boo::b :" << class2str(&Foo::b) << std::endl;
};
where the primary template of class2str_impl may either be deleted (as above), or implement a custom message that a given type does not have a mapped string.

All your overloads currently take object. You might take type instead, or object which hold type:
template <typename T> struct Tag{};
std::string class2str(Tag<double>){ return "Floating";};
std::string class2str(Tag<int>){ return "Fixed Point";};
std::string class2str(Tag<Foo>){ return "Class Foo";};
std::string class2str(Tag<Boo>){ return "Class Boo";};
template<typename Class, typename MemType>
std::string class2str(Tag<MemType Class::*>)
{
return class2str(Tag<MemType> {});
}
With usage:
int main(int argc, char *argv[]) {
int x_a;
double x_b;
Foo F;
Boo B;
std::cout<< "x_a :" << class2str(Tag<decltype(x_a)>{}) <<std::endl;
std::cout<< "x_b :" << class2str(Tag<decltype(x_b)>{}) <<std::endl;
std::cout<< "Foo :" << class2str(Tag<decltype(F)>{}) <<std::endl;
std::cout<< "Boo :" << class2str(Tag<decltype(B)>{}) <<std::endl;
// or
std::cout<< "int :" << class2str(Tag<int>{}) <<std::endl;
};

Different from the tag dispatch and the specialization techniques talked in the other answers, here is a different approach using c++17's constexpr if.
First, we find the type of the member from the member pointer using a trait (mem_type)
Secondly, we write an internal helper function (i.e. helper::class2str()), which uses the compiled time type checking and discard the false branch(i.e. if constexpr), so that we correctly return the data type representation as const char* literals (because we can make the function constexpr)!
Lastly, we will have the main class2str() which actually check the template argument type is a member pointer or not, and do branches as per again using if constexpr. If the template type is a member pointer, we get the member type using the trait mem_type and pass it to the helper::class2str().
#include <type_traits> // std::is_same_v, std::is_member_pointer_v
// trait to get the member type
template<typename Class> struct mem_type {};
template<typename MemType, typename Class> struct mem_type<MemType Class::*> {
using type = MemType;
};
// alias for mem_type<T>
template<typename Type> using mem_type_t = typename mem_type<Type>::type;
namespace helper
{
template<typename Type> constexpr auto class2str() noexcept
{
if constexpr (std::is_same_v<Type, int>) return "Fixed Point";
else if constexpr (std::is_same_v<Type, double>) return "Floating";
else if constexpr (std::is_same_v<Type, Boo>) return "Class Boo";
else if constexpr (std::is_same_v<Type, Foo>) return "Class Foo";
}
}
template<typename Type>
std::string class2str()
{
if constexpr (std::is_member_pointer_v<Type>)
return helper::class2str<mem_type_t<Type>>();
else
return helper::class2str<Type>();
}
Now you can use it like:
std::cout << "x_a :" << class2str<int>() << '\n';
std::cout << "x_b :" << class2str<double>() << '\n';
std::cout << "Boo::b :" << class2str<decltype(&Boo::A)>() << '\n';
std::cout << "Foo::b :" << class2str<decltype(&Foo::b)>() << '\n';
Here is (the complete demo)

Make a class template and specialzie it for various types:
template <typename T> struct TypeNameHelper {};
template <> struct TypeNameHelper<int> { static constexpr std::string_view name = "Integer"; };
template <> struct TypeNameHelper<float> { static constexpr std::string_view name = "Real"; };
I would also add a variable template for a shorter syntax and to preprocess the type if needed:
template <typename T>
inline constexpr std::string_view TypeName = TypeNameHelper<std::remove_cvref_t<T>>::name;
Then you can do:
std::cout << TypeName<int> << '\n';

One idea could be to use the function arguments to call function overloads that only requires a template parameter:
struct Boo {
double A;
Boo()= delete;
Boo(int x) :A(x){};
};
struct Foo {
double A = 33;
Boo b{0};
};
#include <type_traits>
#include <utility>
template<class T> std::string class2str(); // primary
// specializations
template<> std::string class2str<double>(){ return "Floating"; };
template<> std::string class2str<int>(){ return "Fixed Point"; };
template<> std::string class2str<Foo>(){ return "Class Foo"; };
template<> std::string class2str<Boo>(){ return "Class Boo"; };
// taking by argument
template<class T>
std::string class2str(const T&) {
return class2str<std::remove_cv_t<std::remove_reference_t<T>>>();
}
// class member by argument
template<typename Class, typename MemType>
std::string class2str(MemType Class::*) {
return class2str<std::remove_cv_t<std::remove_reference_t<MemType>>>();
}
int main() {
int x_a;
double x_b;
Foo F;
Boo B{1};
std::cout<< "x_a :" << class2str(x_a) <<std::endl;
std::cout<< "x_b :" << class2str(x_b) <<std::endl;
std::cout<< "Foo :" << class2str(F) <<std::endl;
std::cout<< "Boo :" << class2str(B) <<std::endl;
std::cout<< "Foo::A :" << class2str(&Foo::A) <<std::endl;
std::cout<< "Foo::b :" << class2str(&Foo::b) <<std::endl;
std::cout<< "Boo::A :" << class2str(&Boo::A) <<std::endl;
};
Output:
x_a :Fixed Point
x_b :Floating
Foo :Class Foo
Boo :Class Boo
Foo::A :Floating
Foo::b :Class Boo
Boo::A :Floating

A common trick used in the standard library is to use declval.
It's sort of designed for this exact use-case.
Or simpler still
template<typename Class, typename MemType>
std::string class2str(MemType Class::*){
return class2str(*reinterpret_cast<MemType*>(0)); // Never use the value, it's null
}

Related

template specialization and default template parameters and sfinae

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.

C++ Template specialization of member function with a generic type eg. std::vector<T> do not compile

I have a problem with specialization of a member function of a generic struct.
My goal is to specialize the member function Run of Bar with all kinds of std::vector.
#include <iostream>
#include <vector>
// (1) compile
template <typename T>
struct Foo {
T Run() {
std::cout << "Foo not specialized" << std::endl;
return T();
};
};
// (2) compile
template <typename T>
struct Foo<std::vector<T>> {
std::vector<T> Run() {
std::cout << "Foo specialized" << std::endl;
return std::vector<T>();
};
};
template <typename T> struct Bar
{
T Run();
};
// (3) compiles
template<typename T>
T Bar<T>::Run() {
std::cout << "Bar not specialized" << std::endl;
return T();
};
// (3) compiles
template<>
std::vector<bool> Bar<std::vector<bool>>::Run() {
std::cout << "Bar specialized" << std::endl;
return std::vector<bool>();
};
// (4) wont compile: error: invalid use of incomplete type 'struct Bar<std::vector<T> >
template<typename T>
std::vector<T> Bar<std::vector<T>>::Run() {
std::cout << "Bar specialized" << std::endl;
return std::vector<T>();
};
int main(int argc, char const *argv[]) {
Foo<bool> f1;
bool rf1 = f1.Run();
Foo<std::vector<int>> f2;
std::vector<int> rf2 = f2.Run();
Bar<bool> b1;
bool rb1 = b1.Run();
Bar<std::vector<bool>> b2;
std::vector<bool> rb2 = b2.Run();
Bar<std::vector<int>> b3;
std::vector<int> rb3 = b3.Run();
return 0;
};
it would not compile see (4) if i comment it out it work probably.
Is there a way to get this working. Thank you in advance.
Demo not compiling
Demo compiling
Template functions cannot be a partially specialized.
You can do it with a trampoline and overloading. Like Run(){ DoRun(*this) then write DoRun overloads.

Non-type template parameter with default value of type template parameter

I have the following code:
#include <iostream>
#include <string>
#include <type_traits>
struct Foo
{
int i;
int j;
};
template<typename T, T DEFAULT>
class Bar
{
public:
Bar(): mVal(DEFAULT)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
~Bar(){}
Bar(const T &i) : mVal(i)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
Bar &operator=(T const &val)
{
mVal = val;
std::cout << "Bar assignment operator with mVal = " << mVal << "\n";
return *this;
}
explicit operator T() const
{
return mVal;
}
private:
T mVal;
};
int main()
{
std::cout << "Hello \n";
Bar<int, 10> bar1;
}
This is working fine in gcc C++14 as long as the first template parameter in Bar is of an integral type. If I want to do Bar<Foo, {}> the following error message is printed:
on-type template parameters of class type only available with '-std=c++2a' or '-std=gnu++2a'
I already expected that. Changing template<typename T, T DEFAULT> class Bar to template<typename T, T DEFAULT = {}> class Bar leads to the same error.
Also a template specialization template<typename T> class Bar<T, {}> does not work for the same reason.
I also tried to experiment with std::enable_if_t<std::is_integral<T>::value> but could not find a solution that would work.
Is there any possible way to just write Bar<Foo> and not have to write a separate class like template<typename T, T DEFAULT> class BarDefault and template<typename T> class Bar for it?
Template parameters and template arguments - cppreference.com
A non-type template parameter must have a structural type, which is one of the following types (optionally cv-qualified, the qualifiers are ignored):
lvalue reference type (to object or to function);
an integral type;
a pointer type (to object or to function);
a pointer to member type (to member object or to member function);
an enumeration type;
std::nullptr_t; (since C++11)
a floating-point type;
a literal class type with the following properties:
all base classes and non-static data members are public and non-mutable and
the types of all bases classes and non-static data members are structural types or (possibly multi-dimensional) array thereof. (since C++20)
So basically custom structure as template value parameter is available since c++20.
Demo
You can overcome this problem by providing depending template which job is to provide a default value:
https://godbolt.org/z/RFp_xH
#include <iostream>
#include <string>
#include <type_traits>
struct Foo
{
int i = 42;
int j = 4;
};
std::ostream& operator<<(std::ostream& out, const Foo& a)
{
return out << a.i << ',' << a.j;
}
template<typename T>
struct BarDefaultValue
{
constexpr static T value()
{
return T{};
}
};
template<>
struct BarDefaultValue<int>
{
constexpr static int value()
{
return 42;
}
};
template<typename T, typename D = BarDefaultValue<T>>
class Bar
{
public:
Bar(): mVal(D::value())
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
~Bar(){}
Bar(const T &i) : mVal(i)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
Bar &operator=(T const &val)
{
mVal = val;
std::cout << "Bar assignment operator with mVal = " << mVal << "\n";
return *this;
}
explicit operator T() const
{
return mVal;
}
private:
T mVal;
};
int main()
{
std::cout << "Hello \n";
Bar<int> bar1;
Bar<Foo> bar2;
}
You could make a default value to supply.
template<class T>
constexpr T brace_init_value{};
Then used as:
template<typename T, T DEFAULT = brace_init_value<T> >
class Bar
{
Thanks to #Marek R for his idea.
My solution to the problem is the following code which is compiling with gcc in C++14:
#include <iostream>
#include <string>
#include <type_traits>
template <typename T>
constexpr typename std::enable_if<std::is_class<T>::value, T>::type BarDefaultValue(const int &)
{
return {};
}
template <typename T>
constexpr typename std::enable_if<std::is_integral<T>::value, T>::type BarDefaultValue(const int &ret)
{
return static_cast<T>(ret);
}
struct Foo
{
int i;
int j;
};
std::ostream& operator<<(std::ostream& out, const Foo& a)
{
return out << a.i << ',' << a.j;
}
template<typename T, int DEFAULT = 0>
class Bar
{
public:
Bar(): mVal(BarDefaultValue<T>(DEFAULT))
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
~Bar(){}
Bar(const T &i) : mVal(i)
{
std::cout << "Bar constructor with mVal = " << mVal << "\n";
}
Bar &operator=(T const &val)
{
mVal = val;
std::cout << "Bar assignment operator with mVal = " << mVal << "\n";
return *this;
}
explicit operator T() const
{
return mVal;
}
private:
T mVal;
};
int main()
{
std::cout << "Hello \n";
Bar<int, 10> bar1;
Bar<Foo> bar2;
}
The output of this file is:
Hello
Bar constructor with mVal = 10
Bar constructor with mVal = 0,0

std::variant reflection. How can I tell which type of value std::variant is assigned?

I have a class called foo_t that has a member called bar which could be any one of the types std::string, int, std::vector<double>, etc. I would like to be able to ask foo_t which type bar has been assigned to. I decided to use std::variant.
I've written a solution, but I'm not sure if this is a good use of std::variant. I'm not sure if it matters, but I expect the list of types to possibly grow much bigger in the future. I made an enum class to store which type std::variant is assigned to. My first implementation also available on wandbox:
#include <iostream>
#include <variant>
#include <vector>
#include <string>
enum foo_kind_t {
double_list,
name_tag,
number,
unknown
};
template <typename val_t>
struct get_foo_kind_t {
constexpr static foo_kind_t value = unknown;
};
template <>
struct get_foo_kind_t<int> {
constexpr static foo_kind_t value = number;
};
template <>
struct get_foo_kind_t<std::string> {
constexpr static foo_kind_t value = name_tag;
};
template <>
struct get_foo_kind_t<std::vector<double>> {
constexpr static foo_kind_t value = double_list;
};
class foo_t {
public:
foo_t(): kind(unknown) {}
template <typename val_t>
void assign_bar(const val_t &val) {
static_assert(get_foo_kind_t<val_t>::value != unknown, "unsupported assignment");
kind = get_foo_kind_t<val_t>::value;
bar = val;
}
foo_kind_t get_kind() {
return kind;
}
template <typename val_t>
val_t get_bar() {
if (get_foo_kind_t<val_t>::value != kind) {
throw std::runtime_error("wrong kind");
}
return std::get<val_t>(bar);
}
private:
foo_kind_t kind;
std::variant<
int,
std::string,
std::vector<double>
> bar;
};
template <typename val_t>
void print_foo(foo_t &foo) {
std::cout << "kind: " << foo.get_kind() << std::endl;
std::cout << "value: " << foo.get_bar<val_t>() << std::endl << std::endl;
}
int main(int, char*[]) {
// double_list
foo_t b;
std::vector<double> b_val({ 1.0, 1.1, 1.2 });
b.assign_bar(b_val);
std::cout << "kind: " << b.get_kind() << std::endl;
std::cout << "value: vector size: " << b.get_bar<std::vector<double>>().size() << std::endl << std::endl;
// name_tag
foo_t d;
std::string d_val("name");
d.assign_bar(d_val);
print_foo<std::string>(d);
// number
foo_t c;
int c_val = 99;
c.assign_bar(c_val);
print_foo<int>(c);
// unknown
foo_t a;
std::cout << a.get_kind() << std::endl;
return 0;
}
Is this a good way to do it? Is there a way having better performance? Is there a way that requires less code to be written? Is there a way that doesn't require C++17?
If you only need to ask "Is this variant of type X ?" for a single type X, then I recommend that you prefer std::holds_alternative over std::variant::index because the line of code is easier to read and will not have to be updated if the index of the type in the variant changes in the future.
Example:
if (std::holds_alternative<X>(my_variant)) {
std::cout << "Variant is of type X" << std::endl;
}
Using std::variant::index to check stored type at runtime.
There is a solution with type traits
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
using MyVariant = std::variant<int, std::string>;
enum class MyVariantType { integer, string };
template <MyVariantType Type, typename T> struct is_variant_type : std::false_type {};
template <> struct is_variant_type<MyVariantType::integer, int > : std::true_type {};
template <> struct is_variant_type<MyVariantType::string , std::string> : std::true_type {};
template<MyVariantType VT>
bool check_variant_type(const MyVariant& myvar)
{
return std::visit([&](const auto& arg) {
return is_variant_type<VT, std::decay_t<decltype(arg)>>::value;
}, myvar);
}
int main(int argc, char* argv[])
{
MyVariant a = int(10);
MyVariant b = "Hello";
std::cout << check_variant_type<MyVariantType::integer>(a);
std::cout << check_variant_type<MyVariantType::integer>(b);
std::cout << check_variant_type<MyVariantType::string>(a);
std::cout << check_variant_type<MyVariantType::string>(b);
return 0;
}

How to specialize methods for multiple types at once

I have code like this:
template< class T >
struct Value
{
/* quite a lot of other functions which I do not want to specialize, too */
void print( void );
};
template<> void Value< short int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< long int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned short int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< unsigned long int >::print() { std::cout << "is integral" << std::endl; }
template<> void Value< float >::print() { std::cout << "is floating point" << std::endl; }
template<> void Value< double >::print() { std::cout << "is floating point" << std::endl; }
template<> void Value< long double >::print() { std::cout << "is floating point" << std::endl; }
template< class T > void Value<T>::print() { std::cout << "unsupported type" << std::endl; }
int main( void )
{
Value< float >().print();
Value< double >().print();
Value< short >().print();
Value< char >().print();
}
Output:
is floating point
is floating point
is integral
unsupported type
I want to change this in order to reduce code duplication, especially, because the code body is much longer than a simple std::cout. To illustrate the direction I want to go, the simplest idea coming to mind would be to use macros:
#define TMP(T) \
template<> void Value<T>::print() { std::cout << "is integral" << std::endl; }
TMP( short int )
TMP( int )
TMP( long int )
TMP( unsigned short int )
TMP( unsigned int )
TMP( unsigned long int )
#undef TMP
#define TMP(T) \
template<> void Value<T>::print() { std::cout << "is floating point" << std::endl; }
TMP( float )
TMP( double )
TMP( long double )
#undef TMP
But I want to get it to work using C++11 template magic. I already tried using std::enable_if, but I just can't get it to work. E.g. this
template< class T >
void Value<
typename std::enable_if< std::is_integral<T>::value, T >::type
>::print( void )
{
std::cout << "is integral" << std::endl;;
}
gives me
test.cpp:26:24: error: invalid use of incomplete type ‘struct Value<typename std::enable_if<std::is_integral<_Tp>::value, T>::type>’
>::type >::print( void )
^
test.cpp:14:8: error: declaration of ‘struct Value<typename std::enable_if<std::is_integral<_Tp>::value, T>::type>’
struct Value
and using std::enable_if on the return type:
template< class T >
typename std::enable_if< std::is_integral<T>::value, void >::type
Value<T>::print( void )
{
std::cout << "is integral" << std::endl;;
}
gives me:
test.cpp:41:1: error: prototype for ‘typename std::enable_if<std::is_integral<_Tp>::value, void>::type Value<T>::print()’ does not match any in class ‘Value<T>’
Value<T>::print( void )
^
test.cpp:16:17: error: candidate is: static void Value<T>::print()
static void print( void );
Of course there are many similar questions already:
http://www.cplusplus.com/forum/beginner/151879/
match multiple types for template specialization resolution
Template specialization for multiple types
How to do one explicit specialization for multiple types?
But they often are about simple functions, not methods of templated classes. Also they often don't strictly separate declaration and definition as I want to.
This questions sounds very similar, but it specializes the method not in respect to the struct/class template argument, but in respect to another template argument.
I just can't seem to apply the answers to my specific problem, because of the aforementioned errors. Also I don't want to specialize the whole class, because the class itself shares many methods which are identical for all types T. I don't want to trade one copy-paste code for another.
Bonus for explaining why the two error messages happen. What rule am I breaking. To me it seems that ...::type isn't substituted at all with T or void.
You could use tag dispatch. Define your tags:
namespace tag {
struct integral{};
struct floating{};
struct error{};
}
Define their mapping from various types:
namespace detail
{
template<typename T, typename = void>
struct get_tag : tag::error{};
template<typename T>
struct get_tag<T, std::enable_if_t<std::is_integral<T>::value>> : tag::integral{};
template<typename T>
struct get_tag<T, std::enable_if_t<std::is_floating_point<T>::value>> : tag::floating{};
}
And define your function for each supported tag:
void print(tag::error){
std::cout << "unsupported type" << std::endl;
}
void print(tag::integral){
std::cout << "is integral" << std::endl;
}
void print(tag::floating){
std::cout << "is floating" << std::endl;
}
While forwarding to it from your static method:
template< class T >
struct Value
{
static void print( void ){
::print(detail::get_tag<T>{});
}
};
demo
This will work, but you want char not to be treated as an integral, so you may want to define your own trait, that only one of the listed traits (explained in more detail + C++11 version here):
template<typename T, typename... Others>
struct is_any : std::disjunction<std::is_same<T, Others>...>
{
};
Now you can write the following for your integral tag
template<typename T>
struct get_tag<T, std::enable_if_t<
is_any<T, short, int, long, unsigned short, unsigned, unsigned long>::value>> : tag::integral{};
and the result will be exactly as you wanted.
demo
If your want to make your prints member functions, you can make them templates to avoid compile errors for unsupported types:
template< class T >
struct Value
{
static void print( void ){
Value obj;
obj.print(detail::get_tag<T>{});
}
private:
template<typename U = T>
void print(tag::error){
std::cout << "unsupported type" << std::endl;
}
template<typename U = T>
void print(tag::integral){
std::cout << "is integral" << std::endl;
}
template<typename U = T>
void print(tag::floating){
std::cout << "is floating" << std::endl;
}
};
demo
Alternatively, in C++1z you can use constexpr if:
static void print( void ){
using this_tag = detail::get_tag<T>;
if constexpr(std::is_base_of<tag::floating, this_tag>::value) {
std::cout << "is floating" << std::endl;
} else if constexpr(std::is_base_of<tag::integral, this_tag>::value) {
std::cout << "is integral" << std::endl;
} else {
std::cout << "unsupported type" << std::endl;
}
}
demo
To allow SFINAE for your struct, you have to add extra parameter:
template<class T, typename AlwaysVoid = void>
struct Value;
And then
template<class T>
struct Value<T, std::enable_if_t<std::is_integral<T>::value>>
{
void print()
{
std::cout << "is integral" << std::endl;;
}
};