Reputation: 2220
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
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
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 c++17 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;
}
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
Reputation: 1
With c++20 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;
}
Upvotes: 1
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