【问题标题】:reducing syntax "noise" without using a macro在不使用宏的情况下减少语法“噪音”
【发布时间】:2017-09-10 19:04:55
【问题描述】:

我正在尝试找到一种方法来减少一些语法“噪音”,而无需借助宏。对于以下代码:

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

摆脱一些static constexpr const char*(更不用说其他)语法会很好,因为重复tag2tag3等会很烦人。另外,唯一的部分所有真正有趣的是tag1,其余的是“噪音”。直接的解决方案使用宏:

#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>());

基于宏的MAKE_TAG(tag2); 语法已经消除了所有“噪音”,使tag2 非常突出。宏的另一个好处是tag_name 可以轻松转换为字符串文字,从而防止复制粘贴错误。

一个“明显”的可能解决方案可能是通过name as a template argument

template<const char* name> base { ... };
struct tag3 final : private base<"tag3"> {};

但那是not supported by C++。下面answer 中的一个巧妙解决方法是使用可变参数模板:

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

这确实减少了很多噪音,但需要写't', 'a', 'g', '4' 而不是"tag4"。运行时解决方案相当简洁

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

但这并不完全令人满意,因为 tag5 现在可以被实例化,理想情况下这是没有意义的。还有,现在tag5要写三遍,不是很DRY

有没有办法进一步简化(即减少打字)上面的代码? ...不使用宏?

【问题讨论】:

  • 请注意std::string 没有constexpr 构造函数,您将无法在constexpr 上下文中调用name
  • 我不清楚为什么需要static constexpr const char* name = "tag1"; 以外的任何东西?所有其他样板的目的是什么?

标签: c++ templates macros


【解决方案1】:

如果您愿意单独输入字符,我们可以执行以下操作:

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

这样称呼它:

std::cout << tag1::name() << std::endl;
std::cout << tag2::name() << std::endl;

我不得不在派生类中添加using base::name,因为您使用的是私有继承。如果继承变为protectedpublic,则不需要。

基础中name() 函数的要点是创建一个字符数组,我们可以从中构造一个字符串。我们使用variadic parameter pack expansion 创建数组。

【讨论】:

  • @Ðаn: 抱歉,只是感觉不合适。关于在编译时将“abc”转换为abc。我认为这还不太可能(可能是使用宏,但这会破坏你的问题的目的)。
  • @Ðаn:这是值得商榷的。 here是对这种宏的一种尝试。
【解决方案2】:

如果你只想获取类型的名称,你可以这样做(至少对于 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>();

唯一的缺点是在返回的字符串末尾有一个尾随 ']'。

如果您愿意放弃 constexpr 要求,那么您可以轻松删除那个讨厌的最后一个字符。

请注意,这是非标准且不可移植的。

【讨论】:

    猜你喜欢
    • 2011-06-10
    • 1970-01-01
    • 1970-01-01
    • 2016-10-02
    • 1970-01-01
    • 2021-02-23
    • 2020-04-10
    • 2016-02-13
    • 1970-01-01
    相关资源
    最近更新 更多