有没有一种方法可以从`std::initializer_list`创建一个用户定义字面量?

5

就像这个主题中的问题:是否有一种方法可以从std::initializer_list创建用户定义字面量?

我正在尝试做这样的事情:

template <typename T> inline
std::initializer_list<T> const & operator "" _lit(std::initializer_list<T> const & list)
{
    return std::move(list); // I am not sure, but this line might cause undefined behavior... well I'll think about it latter...
}

int main()
{
    { 10, 20, 30, 40 }_lit // Error: identifier '_lit' is undefined;

    return 0;
}

但似乎编译器无法理解我正在尝试调用 operator""_lit({10, 20, 30, 40});。有什么方法可以解决吗?


编辑:
很抱歉,这只是XY问题的另一个例子...
让我详细解释一下

我正试图“扩展”当前C++语法(这是一个有趣的小项目...)

主要想法是简化这个:

if ((val_1 == value) && (val_2 == value) && (val_3 == value)) { /* ... */ }

我将尽力做到最好。以下是需要翻译的内容:

转化为类似这样的内容:

if (std::initializer_list<T>{val_1, val_2, val_3} == value)

当然,我提供了一个额外的运算符:

template <typename T> inline
bool operator==(std::initializer_list<T> const & list, T const & ref)
{
    for (auto const & element : list)
    {
        if (element == ref) { /* Do nothing. */ }
        else
        {
            return false;
        }
    }
    return true;
}

一切都很好,但我不喜欢在花括号前面输入`std::initializer_list<T>`...否则,编译器会选择默认版本的`operator==()`,并导致编译错误...
字面值作为一种想法出现,目的是将`if (std::initializer_list<T>{val_1, val_2, val_3} == value)`更改为`if ({val_1, val_2, val_3}_lit == value)`。

5
不行。听起来像是 XY 问题。针对这个问题的使用场景是什么? - NathanOliver
2
cppreference列出了UDL的各种形式:https://en.cppreference.com/w/cpp/language/user_literal。但这个形式没有列出。因此,它是不允许的。如果你真的想要确定的话,可以查看标准本身。 - Justin
你应该只需要放弃 std::initializer_list<T> 并写成 if ({val_1, val_2, val_3} == value),但是请不要在你不拥有的类型上重载运算符。 - Justin
@NathanOliver 是的。 - PatrykB
2
用户自定义字面值是带有用户自定义后缀的字面值。花括号列表首先不是一个字面值。 - n. m.
显示剩余4条评论
4个回答

1
你无法为std::initializer_list创建用户定义字面量。 幸运的是,C ++17提供了一个非常酷的新工具类模板参数推导,允许我们只使用类模板的名称,编译器将确定模板参数需要什么,因此我们不必指定它们。 这意味着你可以利用std::array并使你的代码变得更简洁。
template<typename T, std::size_t N>
bool operator==(std::array<T, N> const & list, T const & ref)
{
    for(auto const& e : list)
        if (e != ref)
            return false;
    return true;
}

int main()
{
    using std::array;
    if (array{4,4,4,4,4} == 4)
        std::cout << "all 4";
}

除了使用语句外,_litarray之间只有一个额外的字符。

5
不要在类型的命名空间之外过载运算符,这会导致令人惊讶和烦人的行为。不要将运算符注入到 namespace std 中,因为这将使您的程序构建失败。简而言之,在 std 模板上不要重载运算符。并且修改您的示例以避免出现代码异味。 :) - Yakk - Adam Nevraumont
@Yakk-AdamNevraumont 如果我创建一个“tag”类,其中包含一个私有成员std::array<T, N> _list,一个构造函数:tag(std::array<T, N> const & list) : _list(std::move(list)) {}和一个友元重载的bool operator==(std::array<T, N> const & list, T const & ref)?这样能解决“格式不正确的程序”问题吗?最终结果将如下所示:if (tag{4,4,4,4,4} == 4) {/* .... */} - PatrykB
抱歉,我犯了一个错误。使用标签类时,运算符应该像这样: friend bool operator==(tag<T, N> const & list, T const & ref);而不是我上面发布的那个。 - PatrykB
1
@cukier9a7b5 我会直接继承 std::array。这样就可以让 begin/end 的东西正常工作了。 - Yakk - Adam Nevraumont

1

来自评论:

@NathanOliver 我正在尝试“扩展”当前的C++语法(这是一个有趣的小项目...)主要思想是简化这个:if ((val_1 == value) && (val_2 == value) && (val_3 == value)) 到这个:if (std::initializer_list{val_1, val_2, val_3} == value)(当然还需要重载运算符:bool operator==(std::initializer_list const & list, T const & ref)...)我希望省略掉需要输入std::initializer_list的部分,我想到了可以将其更改为自定义字面量

看起来你需要这样的东西:

template<typename T>
bool allEqualTo(T a, T b)
{
    return a == b;
}

template<typename T, typename... TArgs>
bool allEqualTo(T a, T b, TArgs... args)
{
    return allEqualTo(a, b) && allEqualTo(a, args...);
}

if (allEqualTo(value, val_1, val_2, val_3)) {
    ....
}

1
如果您使用范围库,您可以直接使用all_of:
// Using Range-v3: https://ericniebler.github.io/range-v3/index.html
if (ranges::v3::all_of({val_1, val_2, val_3},
    [value](auto const& other) { return value == other; })) {
    // ...
}

你可以使用一个助手来进一步简化它:
// Note: Prior to C++17, this could lead to ODR violations.
// After C++17, this will be an inline variable, thus this is fine.
// If using in C++14 or before, write std::equal_to<>{} instead of std::equal_to{}.
constexpr auto equal_to = boost::hof::partial(std::equal_to{});

// ...

if (ranges::v3::all_of({val1, val_2, val_3}, equal_to(value))) {
    // ...
}

演示


由于某种原因,我总是忘记 boost 是多么有用。谢谢。 - PatrykB

1
template<class T, std::size_t N>
struct any_of:std::array<T, N> {
  #define MAKE_OPERATOR( OPERATOR ) \
    template<class U, \
      std::enable_if_t< std::is_same<void, std::void_t< \
        decltype( std::declval<T const&>() == std::declval<U const&>() ) \
      >>{}, bool> =true \
    > \
    friend bool operator OPERATOR ( any_of const& lhs, U const& rhs) { \
      return std::any_of( \
        lhs.begin(), lhs.end(), \
        [&](auto&& lhs){ return lhs OPERATOR rhs; } \
      ); \
    } \
    template<class U, \
      std::enable_if_t< std::is_same<void, std::void_t< \
        decltype( std::declval<U const&>() == std::declval<T const&>() ) \
      >>{} && !std::is_same< U, any_of >{} , bool> =true \
    > \
    friend bool operator OPERATOR ( U const& lhs, any_of const& rhs) { \
      return std::any_of( \
        rhs.begin(), rhs.end(), \
        [&](auto&& rhs){ return lhs OPERATOR rhs; } \
      ); \
    }
  MAKE_OPERATOR(==)
  MAKE_OPERATOR(!=)
  MAKE_OPERATOR(<)
  MAKE_OPERATOR(<=)
  MAKE_OPERATOR(>=)
  MAKE_OPERATOR(>)
  #undef MAKE_OPERATOR
  explicit any_of( std::array<T, N> arr):std::array<T, N>(std::move(arr)) {}
  template<class...Ts>
  explicit any_of( T t, Ts... ts ):std::array<T, N>{ std::move(t), std::move(ts)... } {}
  any_of( any_of const& )=delete;
  any_of& operator=( any_of const& )=delete;
  any_of()=delete;
};
template<class T, std::size_t N>
any_of(T(&)[N]) -> any_of<T,N>;
template<class T, class...Ts>
any_of(T, Ts...) -> any_of<T, 1+sizeof...(Ts)>;

测试代码:

if (any_of{1,2,3} == 2) {
    std::cout << "2 is there\n";
}
if (! (any_of{1,2,3} == 7) ){
    std::cout << "7 is not there\n";
}

if (any_of{1,2,3} == any_of{5,6,1}) {
    std::cout << "overlap!\n";
}
if (!(any_of{1,2,3} == any_of{5,6,7})) {
    std::cout << "no overlap!\n";
}

实时示例

编译器中的输出:

2 is there
7 is not there
overlap!
no overlap!
各种比较运算符都被支持。
跨类型的双 any_of,例如:
any_of{1,2,3} == any_of{3.14, 5.7, 1.0}

由于any_of==都起作用,所以编译将失败。


这正中要害。 - PatrykB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接