Why std::get does not work with variables?

You wrote yourself that

std::get is a template function and it requires a template argument to be known during the compilation

The value of a local variable is not (in the general case) known during compilation; a local variable's value is a runtime property. As such, a local variable cannot be used as a template argument.

If you want to use it as one, you have to make it a compile-time value. This is achieved by making it constexpr (as you've also stated in the question).


Template non-type parameters, like the size_t that std::get<> takes, must be compile time constants.

Your auto a is not a compile time constant. In your specific case, you can prove that the value a has at that point will never vary and always be 0.

But C++ is a strongly typed language that relies on explicit types provided by the programmer. At the point where std::get<a> is evaluated, the only thing C++ permits itself to know about a is that it is a non-const non-constexpr local variable of type std::size_t.

Thus if std::get<a> works, so must:

int main(int argv, char ** argc) {
  UserInfo s{"Edmund", "[email protected]", "Denver street 19"};
  std::size_t a = argv; // number of arguments
  std::cout << std::get<a>(s) << std::endl;
}

std::get<std::size_t> is a nothrow function, and it is not permitted to fail at runtime. If you call this code with 100 arguments, the above code cannot possibly work.

Second, while your UserInfo is 3 identical types, std::get<size_t>(tuple<a,b,c>) works when the types are not the same. So

using UserInfo = std::tuple<int, std::string, double>;

then std::get< argv >( some_user_info ) also has to work. And in this case, the type it returns can be any one of three types -- but C++ demands that all expressions have one type.

The short version is "the language standard says so". The longer version is "In the general case, your code doesn't work".

Now, you can solve your code in your specific case with a few changes.

using UserInfo = std::array<std::string, 3>;

now UserInfo is known to have 3 uniform types.

std::cout << s[a] << std::endl;

and now you pass in the index, and as the argument to [] isn't a template non-type parameter, it can vary at runtime.

[] is permitted to execute UB if the index is out of bounds. (std::get<a> is not).

Now, the C++ could evolve and the new standard could throw some magic and somehow detect your special case and permit std get to fail at runtime etc. But then every call to std::get is a possible runtime failure, while before it was not; the testing surface of your application just exploded.

Heck, it could auto-detect that auto a = blah was initialized with a constant expression on the previous line, and make its use on the next line automatically be a constant expression.

But then a programmer who knew what they are doing who replaced a call to get_proper_a() with 3 for debugging could have their code spuriously change behaviour as secret type information "leaks" into the code. And when get_proper_a() which actually returns 3 (but the compiler cannot prove it) runs, the code breaks at runtime.