I'm trying to find a way of reducing a bit of syntax "noise" without resorting to a macro. For the following code:
struct base { base() = delete; };
struct tag1 final : private base
{
static constexpr const char* name = "tag1";
};
template <typename T> std::string name() { return T::name; }
// ...
int main()
{
const std::string name1(name<tag1>());
return 0;
}
it would be nice to get rid of the some of the static constexpr const char*
(not to mention the other) syntax as it gets annoying to repeat that for tag2
, tag3
, etc. Plus, the only part of all of that which is really interesting is tag1
, the rest is "noise". The straight-forward solution is to use a macro:
#define MAKE_TAG(tag_name) struct tag_name final : private base { \
static constexpr const char* name = #tag_name; }
MAKE_TAG(tag2);
// ...
const std::string name2(name<tag2>());
The macro-based MAKE_TAG(tag2);
syntax has removed all of the "noise" leaving tag2
quite prominent. An added benefit of the macro is that the tag_name
can easily be turned into a string literal which prevents copy-paste errors.
An "obvious" possible solution might be to pass name
as a template argument
template<const char* name> base { ... };
struct tag3 final : private base<"tag3"> {};
but that's not supported by C++. A clever work-around, from the answer below, is to use variadic template:
template<char... S> struct base { base() = delete;
static std::string name() { return{ S... }; } };
struct tag4 final : base<'t', 'a', 'g', '4'> { };
template <typename T> std::string name() { return T::name(); }
this does reduce much of the noise, but requires writing 't', 'a', 'g', '4'
instead of "tag4"
. A run-time solution is fairly succinct
struct base {
const std::string name;
base(const std::string& name) : name(name) {} };
struct tag5 final : base { tag5() : base("tag5") {} };
template <typename T> std::string name() { return T().name; }
but that's not entirely satisfying as tag5
can now be instantiated, which ideally doesn't make sense. Also, it's now necessary to write tag5
three times, which isn't very DRY.
Is there a way to further simplify (i.e., less typing) the code above? ... without using a macro?
If you're willing to type out characters individually, we can do the following:
template<char... S>
struct base {
base() = delete;
static std::string name(){
return {S...};
}
};
struct tag1 final : private base<'t','a','g','1'>
{using base::name;};
struct tag2 final : private base<'t','a','g','2'>
{using base::name;};
Call it like so:
std::cout << tag1::name() << std::endl;
std::cout << tag2::name() << std::endl;
I had to add using base::name
in the derived classes because you're using private inheritance. If the inheritance becomes protected
or public
, then you don't need that.
The gist of the name()
function in the base is to create a character array that we can construct a string from. We use variadic parameter pack expansion to create the array.
If you just want to get the name of the type, you can do it like this (at least for GCC):
template <typename T> constexpr const char *get_type_name()
{
const char *name = __PRETTY_FUNCTION__;
while (*name++ != '[');
for (auto skip_space = 3 ; skip_space ; --skip_space)
{
while (*name++ != ' ');
}
return name;
}
// ...
auto name1 = get_type_name<int>();
auto name2 = get_type_name<tag1>();
The only drawback is that you have a trailing ']' at the end of the returned string.
If you are willing to drop the constexpr
requirement, then you can easily delete that pesky last character.
Note that this is non-standard and non-portable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With