【问题标题】:Is it possible to assign and verify a constexpr structure in C++14?是否可以在 C++14 中分配和验证 constexpr 结构?
【发布时间】:2019-05-27 02:58:48
【问题描述】:

我实现了getop() in C++。这是一门花里胡哨的课。

其中一个铃声将是一种验证用户在编译时输入有效选项的方法。我已经有一部分用于标志(此处未显示),但我一直在努力使其适用于struct option,目前我还没有取得太大的成功。

首先,代码允许我使用define_option() 函数定义一组选项。我希望能够以任何顺序指定选项结构的字段并对它们进行一些完整性检查。所以我使用模板函数的参数列表。每个参数都是一个当场实例化的类,然后define_option() 为每个字段选择正确的类,提取值。

到这里为止,一切都像魅力一样。非常基本的 C++。

我遇到问题的地方是 define_option() 类。例如,假设我要尝试以下操作:

... define_option(...)
{
    ...
    option opt = { ... };
    static_assert(opt.f_short_name != '\0', "this failed");
    ...
}

在这里我得到一个错误。 static_assert() 告诉我opt 不是常数。

a.cpp:168:1: 错误:静态断言的非常量条件

所以接下来我的步骤是将opt 变量标记为constexpr(错误说要解决static_assert()。)

... define_option(...)
{
    ...
    constexpr option opt = { ... };
    ...
}

现在即使没有static_assert(),代码也无法编译。我收到以下错误:

a.cpp:168:5: 错误:‘args#0’不是常量表达式

我认为在define_option() 函数中它并不是真正可行的,即使可以检查生成的选项列表,如下面的工作示例所示。所以最后 opt 全局变量是在编译时完全定义的静态变量(在运行时不需要在表上重新工作的代码)但这对我没有帮助,因为我无法确保每个选项都会按预期工作。

我在 coliru 上有一个 online running sample

// compiled with: g++ -std=c++14 -o a ~/tmp/a.cpp
//
#include <iostream>

typedef char32_t            short_name_t;
constexpr short_name_t      NO_SHORT_NAME = L'\0';

struct option
{
    short_name_t        f_short_name = NO_SHORT_NAME;
    char const *        f_name = nullptr;
};


template<typename T, T default_value>
class OptionValue
{
public:
    typedef T   value_t;

    constexpr OptionValue<T, default_value>()
        : f_value(default_value)
    {
    }

    constexpr OptionValue<T, default_value>(T const v)
        : f_value(v)
    {
    }

    constexpr value_t get() const
    {
        return f_value;
    }

private:
    value_t     f_value;
};


class ShortName
    : public OptionValue<short_name_t, NO_SHORT_NAME>
{
public:
    constexpr ShortName()
        : OptionValue<short_name_t, NO_SHORT_NAME>()
    {
    }

    constexpr ShortName(short_name_t name)
        : OptionValue<short_name_t, NO_SHORT_NAME>(name)
    {
    }
};


class Name
    : public OptionValue<char const *, nullptr>
{
public:
    constexpr Name()
        : OptionValue<char const *, nullptr>()
    {
    }

    constexpr Name(char const * name)
        : OptionValue<char const *, nullptr>(name)
    {
    }
};


template<typename T, typename F, class ...ARGS>
constexpr typename std::enable_if<std::is_same<T, F>::value, typename T::value_t>::type find_option(F first, ARGS ...args)
{
    return first.get();
}


template<typename T, typename F, class ...ARGS>
constexpr typename std::enable_if<!std::is_same<T, F>::value, typename T::value_t>::type find_option(F first, ARGS ...args)
{
    return find_option<T>(args...);
}



template<class ...ARGS>
constexpr option define_option(ARGS ...args)
{
    option opt =
    {
        .f_short_name =          find_option<ShortName   >(args..., ShortName()),
        .f_name =                find_option<Name        >(args...),
    };

    return opt;
}


constexpr option const opt[] =
{
    define_option(
        Name("--help"),
        ShortName('h')
    )
};


// this test works as expected here
// but it does not work from inside define_option()
//
static_assert(opt[0].f_name[0] != '-', "Wrong start?!");

int main(int argc, char * argv [])
{

    std::cerr << "opt[0].f_short_name = " << opt[0].f_short_name << std::endl;

    return 0;
}

目前我仅限于 C++14。如果有 C++17 解决方案,我仍然会感兴趣。

【问题讨论】:

  • 听起来 C++20 的 consteval 可能会在最终对您可用时对您的“最佳案例 API”有所帮助。

标签: c++ templates c++14 variadic-templates variadic-functions


【解决方案1】:

只需添加一些使调用非常量的内容。说,抛出异常。

template<class ...ARGS>
constexpr option define_option(ARGS ...args)
{
    option opt =
    {
        .f_short_name =          find_option<ShortName   >(args..., ShortName()),
        .f_name =                find_option<Name        >(args...),
    };
    if(/* check fails */) throw something;

    return opt;
}

那么当你尝试做的时候

constexpr option const opt[] =
{
    define_option(
        Name("--help"),
        ShortName('h')
    )
};

如果检查失败,编译器会抱怨这个对define_option的调用不是一个常量表达式,不能用来初始化一个constexpr变量。

如果在运行时调用此函数,您甚至可以装配 something 以产生链接器错误。参见例如this question.

【讨论】:

  • 你真的尝试过吗? (1)它打破了选项将在编译时创建的事实,并且(2)它不会编译失败......也许我做错了其他事情,但此时“抛出”技巧并没有在这种情况下工作。
  • 好的,我看到 coliru 有 g++ 版本 8.2.0。我还在 5.4.0 上。我想这就是为什么它不适合我......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 2011-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多