pass char array properly to templated method - c++

I have some class with enum variable and I wants to stringify that enum. For that reason I added typeValue with stringified values. Also I added separate class for getting stringified values but I can't pass variables in correct way.
Code like this:
#include <iostream>
#include <string>
#include <algorithm>
#define stringify(name) #name
struct MyClass
{
enum class TYPE
{
UNKNOWN = 0,
CLIENT,
SERVER
};
MyClass(TYPE t)
:_type(t)
{
}
TYPE type()
{
return _type;
}
inline static const char* typeValue[10] =
{
stringify(TYPE::UNKNOWN),
stringify(TYPE::CLIENT),
stringify(TYPE::SERVER)
};
private:
TYPE _type;
};
struct EnumStringify
{
/**
* Get enum type string by value
*/
template <class EnumType>
static std::string getEnumTypeName(const EnumType& t, const char (*typeValues)[10])
{
std::string s(typeValues[static_cast<int>(t)]);
return (s.size()) ? s.substr(s.find_last_of(":") + 1) : "";
}
};
int main()
{
MyClass a(MyClass::TYPE::CLIENT);
std::cout << EnumStringify::getEnumTypeName<MyClass::TYPE>(a.type(), MyClass::typeValue).c_str();
}
Errors happened:
main.cpp:55:90: error: no matching function for call to ‘EnumStringify::getEnumTypeName(MyClass::TYPE, const char* [10])’
std::cout << EnumStringify::getEnumTypeName<MyClass::TYPE>(a.type(), MyClass::typeValue).c_str();
^
main.cpp:45:21: note: candidate: template static std::string EnumStringify::getEnumTypeName(const EnumType&, const char (*)[10])
static std::string getEnumTypeName(const EnumType& t, const char (*typeValues)[10])
^~~~~~~~~~~~~~~
main.cpp:45:21: note: template argument deduction/substitution failed:
main.cpp:55:90: note: cannot convert ‘MyClass::typeValue’ (type ‘const char* [10]’) to type ‘const char (*)[10]’
std::cout << EnumStringify::getEnumTypeName<MyClass::TYPE>(a.type(), MyClass::typeValue).c_str();
Please help me to make it correct. Maybe with typedef would be better?

Two issues:
const char(*name)[] defines a pointer to an array of const chars, not strings.
MyClass::typeValue is of type const char* (&)[10] so it cannot be implicitly converted to a pointer to that array.
This works:
class MyClass{
//...
constexpr static const char* typeValue[10] = {
stringify(TYPE::UNKNOWN),
stringify(TYPE::CLIENT),
stringify(TYPE::SERVER)
};
private:
TYPE _type;
};
struct EnumStringify
{
/**
* Get enum type string by value
*/
template <class EnumType>
static std::string getEnumTypeName(const EnumType& t, const char* const (&typeValues )[10])
{
std::string s(typeValues[static_cast<int>(t)]);
return (s.size()) ? s.substr(s.find_last_of(":") + 1) : "";
}
};
I also added constexpr and pass the value by const reference since you do not want to change it.
My advice would to be use std::array, it alleviates all the issues with passing arrays around.
std::array solution
#include <iostream>
#include <string>
#include <algorithm>
#include <array>
#define stringify(name) #name
struct MyClass
{
enum class TYPE
{
UNKNOWN = 0,
CLIENT,
SERVER
};
MyClass(TYPE t)
:_type(t)
{
}
TYPE type()
{
return _type;
}
constexpr static auto typeValue = std::array{
stringify(TYPE::UNKNOWN),
stringify(TYPE::CLIENT),
stringify(TYPE::SERVER)
};
private:
TYPE _type;
};
struct EnumStringify
{
template <class EnumType, class Array>
static std::string getEnumTypeName(const EnumType& t, const Array& array)
{
std::string s(array[static_cast<int>(t)]);
return (s.size()) ? s.substr(s.find_last_of(":") + 1) : "";
}
};
int main()
{
MyClass a(MyClass::TYPE::CLIENT);
std::cout << EnumStringify::getEnumTypeName(a.type(), MyClass::typeValue).c_str();
}
Template variable
If I were to write such code, I would use template variable which the user can specialize to provide strings for their own enums:
//
// "Stringify Library"
#define stringify(name) #name
struct MissingEnumTable{};
template<class Enum>
constexpr MissingEnumTable stringified_enum{};
template <class E>
static std::string getEnumTypeName(const E& t)
{
static_assert(!std::is_same_v<decltype(stringified_enum<E>),MissingEnumTable>,
"Enum E is missing stringified_enum table specialization.");
std::string s(stringified_enum<E>[static_cast<int>(t)]);
return (s.size()) ? s.substr(s.find_last_of(":") + 1) : "";
}
// END
// Library user
// - Provide strings for a custom array
enum class TYPE
{
UNKNOWN = 0,
CLIENT,
SERVER
};
template<>
constexpr auto stringified_enum<TYPE> = std::array{
stringify(TYPE::UNKNOWN),
stringify(TYPE::CLIENT),
stringify(TYPE::SERVER)
};
int main()
{
std::cout << getEnumTypeName(TYPE::UNKNOWN).c_str();
}

Related

Function template argument deduction with user-defined conversion operator

Let's say I have a class that wraps a string literal:
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
Now, I'd like for instances of this wrapped string type to be convertible back to a const char[N] implicitly, in a way I can still access its size. I'd like to be able to do something like:
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
My goal is to have one function defined for foo() that can accept actual string literals as well as wrapped literals. I've tried adding a user-defined conversion operator to the class definition:
using arr_t = char[N+1];
constexpr operator const arr_t&() const { return wrapped_; }
But this gives me the following with clang:
candidate template ignored: could not match 'const char [N]' against 'const literal<7>'
If I change the call to foo() to the following, it works:
foo((const char(&)[8])s);
...which means that the conversion operator works, but not in the context of template argument deduction. Is there any way I can make this work without defining foo() specifically to take a wrapped literal?
The problem you are having is templates never do conversions of the parameters. Since you give it a const literal<7>, that is all it has to work with.
The easy fix for this is to add an overload and then do the cast in the overload to call your string literal version. That should look like
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
which gives you a full example of
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1]; // <- Add const here since literals are const char[N]
constexpr operator const arr_t&() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
Yes, you are adding an overload but all of the important code does not have to be duplicated.
If you can use C++17 and you don't mind a little indirection you could do all of this with one function using a std::string_view and providing literal with a operator std::string_view. That would look like
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1];
constexpr operator std::string_view() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
void foo(std::string_view s) {
std::cout << s.size() << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
foo("testing");
}

Adding more types to template specialization

I have a simple templated class which I instantiate for each type that uses it like so:
template<class T> struct meta {
static const std::string& get_name() {return T::class_name;}
};
template<> struct meta<double> {
static constexpr const char* class_name = "double";
static const char* get_name() { return class_name; }
};
template<> struct meta<std::vector<double> {
static constexpr const char* class_name = "std::vector<double>";
static const char* get_name() { return class_name; }
};
template<> struct meta<std::array<double,2>> {
static constexpr const char* class_name = "double[]";
static const char* get_name() { return class_name; }
};
Then I can use these like so:
int main() {
std::cout << meta<double>::get_name() << '\n';
std::cout << meta<std::vector<double> >::get_name() << '\n';
std::cout << meta<std::array<double,2> >::get_name() << '\n';
}
I use macros to generate variants of the template-specialization for each base-type, but I'm stuck on std::array because it contains a base-type and an integer. I'd like to find out how to add a new templated parameter to an already-templated specialization so that I don't need to specialize for each possible value of integer.
I've tried:
template<int I> struct meta<std::array<double,I>> {
static constexpr const char* class_name = "double[]";
static const char* get_name() { return class_name; }
};
error: ‘class_name’ is not a member of ‘std::array’
From the std::array C++ documentation:
template<
class T,
std::size_t N
> struct array;
So, you need
template<std::size_t I> struct meta<std::array<double, I>> {
static constexpr const char* class_name = "double[]";
static const char* get_name() { return class_name; }
};
Because int and std::size_t are two different types (having different signed-ness), the partial template specialization won't match. Changing int to size_t works.
In summary: Read the documentation.

std::initializer_list value as template parameter

I am trying to do something like this:
template<typename enumType,
std::initializer_list<enumType> values,
std::initializer_list<std::string> mappings>
struct enum_converter {
enumType toEnum(const std::string& literal) { ... }
std::string toString(const enumType value) { ... }
};
I want to use it as follows:
enum test_enum {value_a, value_b};
struct test_enum_converter : public enum_converter<
test_enum,
{value_a, value_b},
{"a", "b"}> {};
GCC tells me:
class std::initializer_list<_Tp> is not a valid type
for a template constant parameter.
Adding const to the type does not change anything. Is there a workaround or a similar solution?
Only integral types, enumerations, pointers and references are allowed as non-type template parameters. std::initializer_list is neither of those.
Do you need the values & mappings to be template parameters? How about making them normal parameters of the constructor, and keeping only the enum as a template param?
template<typename enumType>
struct enum_converter {
enum_converter(std::initializer_list<enumType> values, std::initializer_list<std::string> mappings) : values(values), mappings(mappings)
enumType toEnum(const std::string& literal) { ... }
std::string toString(const enumType value) { ... }
private:
std::initializer_list<enumType> values;
std::initializer_list<std::string> mappings;
};
enum_converter<test_enum> test_enum_converter({...}, {...});
int main()
{
test_enum_converter.toEnum("bla");
}
EDIT
Here is an instance-free alternative:
template<typename enumType>
struct enum_converter {
static init(std::initializer_list<enumType> values, std::initializer_list<std::string> mappings)
{ s_values = values; s_mappings = mappings; }
static enumType toEnum(const std::string& literal) { ... }
static std::string toString(const enumType value) { ... }
private:
static std::initializer_list<enumType> s_values;
static std::initializer_list<std::string> s_mappings;
};
Just call init() once before using the class.
If you need more than one instantiation for a particular enumeration, you can add a disambiguation parameter to the template, like this:
template <typename enumType, typename tag>
struct enum_converter { /*as before*/ };
int main()
{
struct data_strings;
struct user_readable_strings;
enum_converter<test_enum, data_strings>::init({...}, {"a", "b"});
enum_converter<test_enum, user_readable_strings>::init({...}, {"Option A", "Option B"});
}
Pointers can only be template arguments if they are constant expressions, i.e. the address of an object with extern linkage. The following code works, though it's your call whether it's worth it:
typedef char const * charptype;
template <int, charptype> struct item;
template <typename ...> struct econv
{
static constexpr charptype convert(int) { return nullptr; }
};
template <int I, charptype S, typename ...Tail>
struct econv<item<I, S>, Tail...>
{
static constexpr charptype convert(int i)
{
return i == I ? S : econv<Tail...>::convert(i);
}
};
#include <iostream>
extern char const Hello[] = "Hello";
extern char const World[] = "World";
int main()
{
std::cout << econv< item<1, Hello>
, item<2, World> >::convert(2)
<< "\n";
}
By putting the strings into an anonymous namespace and using some helper macros, this approach might be cleaned up a bit to look presentable.

const char array (c style string) template specialization

I want to be able to specialize based on a constant c style string. The problem is that when I call my templatized function the type is const char[N] where 'N' is the size of the string +1 (null character). How can I specialize for all c-style strings?
The following code displays the problem. You can see the specialization for const char [15] gets matched for "const char [15]" but for "const char[5]" it goes to Generic.
Is there any way to do this?
template <typename T>
struct Test {
static const char* type() { return "Generic"; }
};
template <>
struct Test<const char*> {
static const char* type() { return "const char*"; }
};
template <>
struct Test<const char[]> {
static const char* type() { return "const char[]"; }
};
template <>
struct Test<const char[15]> {
static const char* type() { return "const char[15]"; }
};
template <>
struct Test<char*> {
static const char* type() { return "char*"; }
};
template <>
struct Test<char[]> {
static const char* type() { return "char[]"; }
};
template <typename T>
void PrintType(const T& expected) {
std::cerr << expected << " type " << Test<T>::type() << std::endl;
}
int main(int argc, char* argv[]) {
const char* tmp = "const char*";
PrintType(tmp);
PrintType("const char[]");
PrintType("const char[15]");
PrintType("const char[5]");
}
output when run in Windows 7 - VS 2008
const char* type const char*
const char[] type Generic
const char[15] type const char[15]
const char[5] type Generic
A specialization for any array of chars is:
template< std::size_t N >
struct Test< const char[N] > { ... };
However you can no longer return a char* from type(), unless you write more metafunctions to turn a non-type template parameter into its textual representation.
Try this:
#include <cstdio>
#include <string>
template<class T>
void f2(const T& s) // Handle all kinds of string objects
{ std::printf("string object: %s\n", s.c_str()); }
void f2(const char* s) // Handle const char*
{ std::printf("const char*: %s\n", s); }
// ----------------------------------------------------------------------------
template<size_t N>
void f(const char(&s)[N]) // Handle const char array
{ std::printf("const char[%zu]: %s\n", N, s); }
template<size_t N>
void f(char(&s)[N]) // Handle char array
{ std::printf("char[%zu]: %s\n", N, s); }
template<class T>
inline void f(T&& s) // Handle other cases
{ f2(std::forward<T>(s)); }
int main() {
std::string stringObj = "some kind of string object ...";
char charArr[] = "char array";
const char constCharArr[] = "const char array";
const char* constCharPtr = "const char pointer";
f(stringObj);
f(charArr);
f(constCharArr);
f(constCharPtr);
//f("const char array");
}
Output:
string object: some kind of string object ...
char[11]: char array
const char[17]: const char array
const char*: const char pointer
Explanation
f() has two kinds of overloads: One for char arrays and one for "everything else".
f2() handles the "everything else" case.

Build a regexp to read a file using fscanf based on a boost::tuple

I have several files of data which can contains different types of data.
For example i can have a file of : int-int-double :
1,2,0.5
1,3,0.7
or int-char[16]-double :
1,15c48e7d3a4857bb,0.1
2,49b49e45f46aa45b,0.3
I want to build an object which can be considered as a generic reader. All i want to provide is a boost::tuple type and the object must be able to give me an appropriate regular expression to read a file using fscanf
The basic code for that object would be :
template <typename T>
class reader_t<typename T>
{
...
std::string regexp() const;
}
After that, i will instantiate an object giving the appropriate boost::tuple format.
reader_t reader<boost::tuple<int, int, double> > reader;
To read a file i would use the regexp returned by the method 'regexp' :
std::cout<<reader.regexp()<<std::endl;
In this case, it is supposed to give me:
"%d,%d,%lf\n"
I looked over the boost description but i didn't find what i needed.
Has someone an idea ?
This should get you started:
#include <cstddef>
#include <deque>
#include <string>
#include <sstream>
#include <boost/tuple/tuple.hpp>
namespace details {
struct reader_t_helper
{
typedef std::deque<std::string const*> accumulator_t;
template<typename T, int N>
struct format_accumulator
{
static void add(reader_t_helper::accumulator_t& fmts)
{
reader_t_helper::format_accumulator_impl<T, N>(fmts);
reader_t_helper::format_accumulator<T, N - 1>::add(fmts);
}
};
template<typename T>
struct format_accumulator<T, 0>
{
static void add(reader_t_helper::accumulator_t& fmts)
{
reader_t_helper::format_accumulator_impl<T, 0>(fmts);
}
};
private:
template<typename T> struct map_dispatcher;
template<typename T, int N>
static void format_accumulator_impl(reader_t_helper::accumulator_t& fmts)
{
typedef typename boost::tuples::element<N, T>::type elem_t;
fmts.push_front(reader_t_helper::map_dispatcher<elem_t>::map());
}
};
template<> struct reader_t_helper::map_dispatcher<short>
{
static std::string const* map()
{
static std::string const fmt("%hi");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<unsigned short>
{
static std::string const* map()
{
static std::string const fmt("%hu");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<int>
{
static std::string const* map()
{
static std::string const fmt("%i");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<unsigned>
{
static std::string const* map()
{
static std::string const fmt("%u");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<long>
{
static std::string const* map()
{
static std::string const fmt("%li");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<unsigned long>
{
static std::string const* map()
{
static std::string const fmt("%lu");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<long long>
{
static std::string const* map()
{
static std::string const fmt("%lli");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<unsigned long long>
{
static std::string const* map()
{
static std::string const fmt("%llu");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<float>
{
static std::string const* map()
{
static std::string const fmt("%f");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<double>
{
static std::string const* map()
{
static std::string const fmt("%lf");
return &fmt;
}
};
template<> struct reader_t_helper::map_dispatcher<long double>
{
static std::string const* map()
{
static std::string const fmt("%Lf");
return &fmt;
}
};
template<std::size_t N> struct reader_t_helper::map_dispatcher<char[N]>
{
static std::string const* map()
{
static std::string const fmt(map_dispatcher::init());
return &fmt;
}
private:
static std::string init()
{
std::ostringstream oss;
oss << '%' << N << 'c';
return oss.str();
}
};
}
template<typename T>
struct reader_t
{
std::string format() const
{
typedef details::reader_t_helper::accumulator_t fmts_t;
typedef fmts_t::const_iterator citer_t;
static int const t_len = boost::tuples::length<T>::value;
fmts_t fmts;
details::reader_t_helper::format_accumulator<T, t_len - 1>::add(fmts);
std::string ret;
for (citer_t it = fmts.begin(), it_end = fmts.end(); it != it_end;)
{
ret += **it;
ret += ++it != it_end ? ',' : '\n';
}
return ret;
}
};
Add whatever reader_t_helper::map_dispatcher<> specializations you need, as I'm sure I missed some.
Note that if you only need a type list, and not actual values of those types, the implementation could be simplified by using a boost::mpl::vector<T1, T2, ..., Tn> rather than a boost::tuple<T1, T2, ..., Tn>.
Also note that if need be, it would be possible to implement this as a 100% compile-time metafunction through use of boost::mpl::string<>.
EDIT: Added support for char[N]; I missed that requirement the first time around.
I'm not exactly an expert in this, but how about some C++ template metaprogramming.
Consider this for example:
template<class T> struct x {
static const char a = '\0';
static const char b = '\0';
};
template<> struct x<int> {
static const char a = 'd';
static const char b = '\0';
};
template<> struct x<double> {
static const char a = 'l';
static const char b = 'f';
};
You could use it to create a string like this:
string test;
test = "%" + x<int>::a + x<int>::b + ",";
test += "%" + x<int>::a + x<int>::b + ",";
test += "%" + x<double>::a + x<double>::b;
You might want to find some better names for a,b and x, and might need to figure out how to extract the types from a boost::tuple.