Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reducing syntax "noise" without using a macro

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?

like image 460
Ðаn Avatar asked Apr 14 '17 13:04

Ðаn


2 Answers

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;};

Demo

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.

like image 75
AndyG Avatar answered Oct 09 '22 18:10

AndyG


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.

like image 42
Ghostrider Avatar answered Oct 09 '22 20:10

Ghostrider