是否可以使用非常量变量和 as_const() 从元组中检索元素?

Is it possible to retrieve an element from a tuple using a non-const variable and as_const()?

提问人:enoon.erehwon 提问时间:8/25/2023 最后编辑:enoon.erehwon 更新时间:8/25/2023 访问量:79

问:

正如标题所述,我想知道是否可以将变量传递给元组?std::get<>()

我有一个头文件,其中包含一个结构,其中包含许多用于实例化不同对象类型的参数(和函数)。头文件本质上是一个帮助程序,用于在我测试不同的类时减少实例化中的冗余。

我正在尝试调整定义/初始化函数,以允许使用集合和可变元组将自定义参数(非默认值)传递到其中。该集表示与参数关联的索引,元组将包含要替换到参数中的非默认值。如果集合中存在索引(与参数隐式关联),则使用传入的值代替默认值。

我有一个滚动索引,用于跟踪元组中要从中检索元素的下一个位置。每当从元组中检索项时,索引都会更新。i

使用滚动索引的原因是,其中一些对象在其构造函数中吸收了许多参数 (>10),但我通常只需要通过传入 1-3 个无效参数来测试(预期的)非名义行为。因此,对我来说,理想的情况是不必传入一个大小为参数数的元组(并用占位符填充未被替换的值)。虽然这样可以避免“动态”计算下一个索引的需要,因为它将是 1:1 映射,但这对我来说有点不优雅。i

下面是我的代码的简化版本:

#include <iostream>
#include <tuple>
#include <utility>
#include <set>

struct MyStruct
{
    int my_int;
    char my_char;
    std::string my_string;

    template <typename...T>
    void initParams(std::set<int> paramIndices=std::set<int>(), std::tuple<T...> customParams=std::tuple<T...>())
    {   
        auto i=0;

        // size of set and tuple must match
        if(paramIndices.size()==std::tuple_size<std::tuple<T...>>{})
        {
            // if values are present, substitute them

            // these throw an error, which is understandable; i is not const, const template argument is required for std::get<>
            my_int = paramIndices.contains(0) ? std::get<i++>(customParams) : 0;
            my_char = paramIndices.contains(1) ? std::get<i++>(customParams) : 'a';
            my_string = paramIndices.contains(2) ? std::get<i++>(customParams) : "hello";

            // attempt to use as_const() to introduce const-ness
            // why do these throw an error as well?
            my_int = paramIndices.contains(0) ? std::get<std::as_const(i++)>(customParams) : 0;
            my_char = paramIndices.contains(1) ? std::get<std::as_const(i++)>(customParams) : 'a';
            my_string = paramIndices.contains(2) ? std::get<std::as_const(i++)>(customParams) : "hello";
        }
    }
    
    /* do stuff with params
    ...
    */

};

int main()
{
    std::set<int> s{0, 2};
    std::tuple t{500, "world"};

    MyStruct().initParams(s, t);
}

我将 CMake 用于更大的项目,但对于这个简化版本,我编译为:g++ -std=c++20 test.cpp

as_const 是在 C++17 中引入的,set 方法 contains() 是在 C++20 中引入的。

C++ C++17 常量 stdtuple

评论

5赞 Pepijn Kramer 8/25/2023
get<i++>???将编译时构造(模板参数)与运行时变量混合是行不通的。std::tuple 是一个辅助类,主要用于(元)模板编程。对于运行时代码,只需使用名称良好的成员定义自己的结构即可。(该结构可以是 constexpr,因此您可以创建这些结构的编译时 std::array)
0赞 Eljay 8/25/2023
请尝试编译您的代码,并修复与您的问题无关的问题。这样一个最小的可重复示例将是最有帮助的,否则其他问题会分散您的问题的注意力,并产生大量评论。
1赞 Weijun Zhou 8/25/2023
你似乎相信可以神奇地使一个表情变成一个恒定的表情。不,它不能,并且由于根本原因,没有办法编写一个函数来做到这一点。as_const
0赞 user1095108 8/25/2023
@PepijnKramer实际上可以做类似的事情,比如 tuple[i++],google for the hack。
0赞 Pepijn Kramer 8/25/2023
@user1095108 对此,我回答说:没有技巧。呵呵,你甚至自己称它为黑客;)它仍然试图将方形钉子放入圆孔中(或相反)。而且我仍然认为人们过多地使用 std::tuple/std::p air 是因为它“方便”,问题是它们在语义上毫无意义(因此应该只在语义尚不明确时使用,比如在库中)

答:

3赞 Ben Voigt 8/25/2023 #1

as_const只需通过添加限定符来修改类型。它不返回编译时常量,而编译时常量是模板参数所必需的。标准中定义的术语常量表达式表示“编译时”常量const

常量表达式可以在翻译过程中计算

从 https://eel.is/c++draft/expr.const#note-1


也就是说,尽管您的代码无法按编写的方式工作,但一些递归 constexpr 函数元编程应该能够执行将参数分配给成员的整体任务。

评论

0赞 enoon.erehwon 8/25/2023
感谢您做出区分并提供参考。我会考虑你的建议,看看我想出什么;一般(最初)的想法在我看来似乎是合理的,你已经提出了一条更可行的路径来到达那里。
2赞 Jarod42 8/25/2023 #2

您可以使用 std::index_sequence 而不是传递编译时索引:std::set

template <std::size_t ... Is, typename...Ts>
void initParams(std::index_sequence<Is...>, std::tuple<Ts...> customParams)
{   
    static_assert(sizeof...(Is) == sizeof...(Ts));

    auto members = std::tie(my_int, my_char, my_string);
    std::apply([&](const auto&... args){ ((std::get<Is>(members) = args), ...) ; }, customParams);
}

演示

评论

0赞 enoon.erehwon 8/25/2023
谢谢,这实际上实现了我的基本目标,而且非常优雅!演示与我的实际代码更为相同,结构的变量已经保留了一些默认值,但被替换为一些可选的相应值。
0赞 Pepijn Kramer 8/25/2023 #3

也许如果我明白你要做的是不必为你不想初始化的所有参数输入值。如果是这样,那么使用 C++20 您可以这样做:

#include <cassert>
#include <optional>
#include <string>

using namespace std::string_literals;

struct MyStruct
{
    std::optional<int> int1{};
    std::optional<int> int2{};
    std::optional<std::string> string1{};
    std::optional<std::string> string2{};
};


int main()
{
    MyStruct s{ .int1 = 42, .string2 = "Hello" };

    assert(s.int1.value() == 42);
    assert(s.int2.has_value() == false);
    assert(s.string1.has_value() == false);
    assert(*s.string2 == "Hello"s);
    
    return 0;
}

评论

0赞 enoon.erehwon 8/25/2023
不,不完全是。所有参数都将具有默认的初始化值,即确定值。正如我所提到的,主要思想是能够根据集合中存在的索引替换这些默认值(部分或全部)。请参阅上面@Jarod42的演示,它与我在问题中放置的示例代码略有不同,但它更接近我的实际代码(参数具有默认值)。与元组一起使用可以解决该问题,并满足编译时约束。std::index_sequence
0赞 Pepijn Kramer 8/25/2023
好的,至少我现在了解了 std::tie 和成员。