-1

As title states, I'm wondering if it's possible to pass in a variable to std::get<>() of a tuple?

I have a header file, which contains a struct that holds numerous params (and functions) that are used to instantiate different object types. The header file is essentially a helper to reduce redundancy in instantiation, when I am testing different classes.

I am trying to tweak the defining/initializing functions, to allow for custom parameters (non-default) to be passed into them, using a set and a variadic tuple. The set represents the indices associated with a param, and the tuple will contain the non-default values to substitute into the params. If an index (implicitly associated with a param) is present in the set, the passed in value is used in place of a default.

I have a rolling index, i, that keeps track of the next position to retrieve an element from, in the tuple. The index is updated whenever an item is retrieved from the tuple.

Reason for using a rolling index is because some of these objects take in many params (>10) into their constructor, but I often only need to test (expected) off-nominal behaviour by passing in just 1-3 invalid parameters. As such, it would be ideal for me to not have to pass in a tuple whose size is the number of params (and filled with placeholders for the values not being substituted). While that would obviate the need to "dynamically" compute the next index i, since it would be a 1:1 mapping, it's a bit inelegant to me.

Below is a simplified version of my code:

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

I am using CMake for the larger project, but for this simplified version, I am compiling as: g++ -std=c++20 test.cpp

as_const was introduced in c++17, and the set method contains() was introduced in c++20.

  • 5
    `get` ??? Mixing compile time constructs (template arguments) with runtime variables is not going to work. std::tuple is a helper class primarily used in (meta) template programming. For runtime code just define your own structs with well-named members. (that struct can be constexpr so you can make a compile time std::array of those structs) – Pepijn Kramer Aug 24 '23 at 17:57
  • Please try compiling your code, and fix the issues that are not relevant to your question. Such a [mcve] will be most helpful, otherwise the other issues will distract from your question, and generate a lot of comments. – Eljay Aug 24 '23 at 18:01
  • 1
    You seems to believe that `as_const` can magically make an expression become a *constant expression*. No it cannot, and there is no way to write a function that does this, for fundamental reasons. – Weijun Zhou Aug 24 '23 at 18:05
  • @PepijnKramer it is actually possible to do something similar, like tuple[i++], google for the hack. – user1095108 Aug 24 '23 at 18:06
  • @user1095108 To that I answer : No tricks. hehe you even call it a hack yourself ;) it is still trying to put a square peg into a round hole (or the other way around). And I still think people use std::tuple/std::pair too much becuase it is "convenient", the thing is they are semantically meaningless (and thus should only be used when semantics isn't clear yet, like inside libraries) – Pepijn Kramer Aug 24 '23 at 18:13
  • @Op Learn more about [constexpr](https://en.cppreference.com/w/cpp/language/constexpr) (or consteval) and re-evaluate your approach. – Pepijn Kramer Aug 24 '23 at 18:15
  • @PepijnKramer thanks for the insight into compile-time vs. runtime contexts. In this case, the types of the params are heterogenous (with the possibility of different params being passed in at any time) hence, my use of tuples. I do need to gain a better grasp on these concepts, `const`, `constexpr`, `consteval`. – enoon.erehwon Aug 24 '23 at 18:28
  • @Eljay thank you! Fixed and noted for future reference. – enoon.erehwon Aug 24 '23 at 18:29
  • @WeijunZhou Not quite that I believed `as_const` would make a constant expression. It was moreso that my confusion lay in conflating the core meaning of `const` with `constexpr` (compile-time constants). Thank you for pointing that out! – enoon.erehwon Aug 24 '23 at 18:44

3 Answers3

3

as_const just modifies the type, by adding the const qualifier. It does not return a compile-time constant, which is what a template parameter requires. The term constant expression defined in the Standard means "compile-time" constant:

Constant expressions can be evaluated during translation

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


That said, although your code cannot work as written, a bit of recursive constexpr function metaprogramming should be able to perform your overall task of allocating parameters to members.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thank you for making the distinction and for providing the reference. I'll think on your suggestion and see what I come up with; the general (initial) idea seems plausible to me, and you've suggested a more feasible path to arrive there. – enoon.erehwon Aug 24 '23 at 18:31
2

You might use std::index_sequence instead of std::set to pass compile time indexes:

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

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thank you, this actually achieves my underlying objective, and quite elegantly! And the demo is more identical to my actual code, with the struct's variables already holding some default value, but being replaced with some optional, corresponding value. – enoon.erehwon Aug 24 '23 at 18:40
0

Maybe if I understand what you are trying to do is not to have to type values for all parameters you do not want to initialize. If so then with C++20 you can do this:

#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;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • No, not quite. All the parameters will have a default initialized value, that is definite. As I mentioned, the main idea is to be able to replace these default values (some or all) depending on which index is present in the set. See @Jarod42's demo above, it's slightly different from the sample code I placed in the question, but it's closer to my actual code (with the parameters having default values). Using `std::index_sequence` with the tuple solves the issue, and satisfies the compile-time constraint. – enoon.erehwon Aug 24 '23 at 19:01
  • Ok, at least I learned about std::tie and members now. – Pepijn Kramer Aug 24 '23 at 19:14