David van rijn
David van rijn

Reputation: 2220

Template specialization using a variable argument

I am trying to wrap a c-style variant in a library. It uses a type enum, which I mean to fill using templates.

A simplified version looks roughly like this:

enum Type
{ 
    Type_Int,
    Type_Double,
    Type_String
};

template<typename T>
Type typeId();

template<> Type typeId<int>()   { return Type_Int;  }
template<> Type typeId<double>(){ return Type_Double;  }
template<> Type typeId<const char*>()   { return Type_String;  }

template<> Type typeId<char[10]>()   { return Type_String;  }

// not allowed
// template<int N> Type typeId<char[N]>()   { return Type_String;  }

https://godbolt.org/z/3GsKv6PEn

I can recognize char[10] just fine, but I want to make a general case of char[N], which is not allowed. So how would I go about recognizing all char[N] types?

ps. It is embedded, so I prefer not to use std::string where possible.

Upvotes: 3

Views: 164

Answers (4)

xryl669
xryl669

Reputation: 3614

What about:

template<int N> Type typeId(char(& a)[N])   { return Type_String;  }

It's not exactly the same syntax, but it'll work the same as the other one since you can specify the type by the template parameter.

See example

And by the way, you don't need a template at all for this mapping. A solution with basic function overload would work:

#include <iostream>

enum Type{ 
    Type_Int,
    Type_Double,
    Type_String,
};

constexpr Type typeId(int)   { return Type_Int;  }
constexpr Type typeId(double){ return Type_Double;  }
constexpr Type typeId(const char*)   { return Type_String;  }

int main(int argc, char** argv)
{
    int e;
    std::cout << typeId(e) << std::endl;
    double d;
    std::cout << typeId(d) << std::endl;
    const char * c;
    std::cout << typeId(c) << std::endl;
    char b[10];
    std::cout << typeId(b) << std::endl;
    char a[11];
    std::cout << typeId(a) << std::endl;

}

The function can be constexpr, so you can extract the Type at compile time too.

Finally, here's a version without a function at all, since it's not strictly required. This has the lowest overhead (understand: none)

#include <iostream>

enum Type{ 
    Type_Int,
    Type_Double,
    Type_String,
};

template <typename T> constexpr Type Map;
template <> constexpr Type Map<int> = Type_Int;
template <> constexpr Type Map<double> = Type_Double;
template <> constexpr Type Map<const char*> = Type_String;
template <size_t N> constexpr Type Map<char[N]> = Type_String;


int main(int argc, char** argv)
{
    std::cout << Map<int> << std::endl;
    std::cout << Map<double> << std::endl;
    std::cout << Map<const char*> << std::endl;
    std::cout << Map<char[10]> << std::endl;
    std::cout << Map<char[11]> << std::endl;
}

Complete example with invert map too (Type to actual C++ type) here

Also, you don't need C++20 for that (might not be available in your embedded development toolbox anyway).

Upvotes: 1

JeJo
JeJo

Reputation: 32952

How would i go about recognizing all char[N] types?

Class specialization should be the preferred way, which works all the time.

However, in your case, you could use if constexpr as follows (Requires or later) to make one typeId() function for all the Type cases:

template<typename T>
inline constexpr Type typeId() /* noexcept */
{
    if constexpr (std::is_same_v<T, int>)  return Type_Int;
    else if constexpr (std::is_same_v<T, double>) return Type_Double;
    else if constexpr (std::is_same_v<T, const char*> 
        || (std::is_array_v<T> 
               && std::is_same_v<std::remove_const_t<std::remove_extent_t<T>>, char>)
        )
        return Type_String;
}

See live demo


If there are multidimensional char arrays exist, you could replace the std::remove_extent_t with the std::remove_all_extents in the above.

Upvotes: 4

Alan
Alan

Reputation: 1

With you can use concept and write a trait/check like std::is_array to check if type is a char array as shown below.

template<class T>
struct is_char_array : std::false_type {};
 
template<>
struct is_char_array<char[]> : std::true_type {};
 
template<std::size_t N>
struct is_char_array<char[N]> : std::true_type {};
template< class T >
inline constexpr bool is_char_array_v = is_char_array<T>::value;

template<typename T> concept checkArray = is_char_array_v<T>;

template<checkArray T> Type typeId()
  { 
    std::cout << "char N version\n";
    return Type_String;  
  }

Working demo

Upvotes: 1

Marek R
Marek R

Reputation: 38092

You can't partially specialize a functions. But you can partially specialize class or struct.

So to resolve this do template logic in a class and then use it in a function:

enum Type {
    Type_Int,
    Type_Double,
    Type_String,
};

namespace detail {
template <typename T>
class TypeId;

template <>
class TypeId<int> {
public:
    static constexpr Type value = Type_Int;
};

template <>
class TypeId<double> {
public:
    static constexpr Type value = Type_Double;
};

template <>
class TypeId<const char*> {
public:
    static constexpr Type value = Type_String;
};

template <int N>
class TypeId<const char[N]> {
public:
    static constexpr Type value = Type_String;
};
}

template <typename T>
constexpr Type typeId()
{
    return detail::TypeId<T>::value;
}

static_assert(typeId<int>() == Type_Int);
static_assert(typeId<double>() == Type_Double);
static_assert(typeId<const char*>() == Type_String);
static_assert(typeId<const char[2]>() == Type_String);
static_assert(typeId<const char[10]>() == Type_String);

https://godbolt.org/z/4zfb47Mvx

To make this more resistant to cv-qualifiers type variations you can use std::remove_cvref_t : https://godbolt.org/z/esGf4Wc8h

Upvotes: 8

Related Questions