Почему std :: get не работает с переменными? - PullRequest
8 голосов
/ 30 мая 2019

Я сталкиваюсь с проблемой понимания того, как функции, особенно функции шаблонов и локальные переменные ведут себя во время компиляции.

Так что этот код хорошо работает с std::get:

enum class UserInfoFields{name, email, address};

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

int main()
{
    UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
    std::cout << std::get<static_cast<size_t>(UserInfoFields::name)>(s) << std::endl;
    return 0;
}

Это, насколько я понимаю, потому что std::get является функцией шаблона и требует, чтобы аргумент шаблона был известен во время компиляции.Это имеет смысл, поскольку static_cast<... дает нам значение во время компиляции.

Что я не понимаю, если я изменю код main() на этот:

 int main()
{
    UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
    auto a = static_cast<size_t>(UserInfoFields::name);
    std::cout << std::get<a>(s) << std::endl;
    return 0;
}

Этоне положено.Я знаю, что должен использовать constexpr, но я хочу знать, почему именно второй код не работает?

Ответы [ 3 ]

5 голосов
/ 30 мая 2019

Вы сами написали, что

std::get - это функция шаблона, для которой требуется знать аргумент шаблона во время компиляции

Значение локальной переменной не (в общем случае), известное во время компиляции; значение локальной переменной является свойством времени выполнения. Таким образом, локальная переменная не может использоваться в качестве аргумента шаблона.

Если вы хотите использовать его как единое целое, вы должны сделать его значением времени компиляции. Это достигается с помощью constexpr (как вы также указали в вопросе).

3 голосов
/ 30 мая 2019

Параметры не типового шаблона, такие как size_t, который принимает std::get<>, должны быть константами времени компиляции.

Ваш auto a не является постоянной времени компиляции. В вашем конкретном случае вы можете доказать, что значение a в этой точке никогда не изменится и всегда будет 0.

Но C ++ - это строго типизированный язык, который опирается на явные типы, предоставляемые программистом. В момент вычисления std::get<a> единственное, что C ++ позволяет себе знать о a, - это то, что это неконстантная локальная переменная non-constexpr типа std::size_t.

Таким образом, если std::get<a> работает, то должны:

int main(int argv, char ** argc) {
  UserInfo s{"Edmund", "edmund@page.me", "Denver street 19"};
  std::size_t a = argv; // number of arguments
  std::cout << std::get<a>(s) << std::endl;
}

std::get<std::size_t> - это функция nothrow, и она не может завершаться ошибкой во время выполнения. Если вы вызываете этот код с 100 аргументами, приведенный выше код не может работать.

Во-вторых, в то время как ваш UserInfo имеет 3 идентичных типа, std::get<size_t>(tuple<a,b,c>) работает, когда типы не совпадают. Так

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

тогда std::get< argv >( some_user_info ) тоже должно работать. И в этом случае тип, который он возвращает, может быть любым из трех типов - но C ++ требует, чтобы все выражения имели тип one .

Краткая версия - "так говорит языковой стандарт". Более длинная версия - «В общем случае ваш код не работает».

Теперь вы можете решить свой код в вашем конкретном случае с небольшими изменениями.

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

теперь UserInfo, как известно, имеет 3 типа униформы.

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

и теперь вы передаете индекс, и, поскольку аргумент [] не является параметром не типового шаблона, он может изменяться во время выполнения.

[] разрешено выполнять UB, если индекс выходит за пределы. (std::get<a> нет).

Теперь, C ++ может развиваться, и новый стандарт может бросить некоторую магию и каким-то образом обнаружить ваш особый случай и разрешить сбой std во время выполнения и т. Д. Но тогда каждый вызов std::get является возможной ошибкой во время выполнения, а до этого не было; поверхность тестирования вашего приложения только что взорвалась.

Черт, он может автоматически обнаружить, что auto a = blah был инициализирован с помощью константного выражения в предыдущей строке, и использовать его на следующей строке автоматически будет константным выражением.

Но тогда программист, который знал, что они делают, заменив вызов get_proper_a() на 3 для отладки, мог бы своим кодом внезапно изменить поведение, поскольку информация секретного типа «просачивается» в код. И когда get_proper_a(), который фактически возвращает 3 (но компилятор не может этого доказать), запускается, код прерывается во время выполнения.

1 голос
/ 30 мая 2019

Это не формальное объяснение, а (надеюсь) легкое для понимания обоснование.

Поскольку C ++ является строго типизированным языком, тип возвращаемого значения std::get должен быть известен компилятору во время компиляции. Но разные версии std::get будут возвращать разные типы - потому что в кортеже могут быть разные типы при разных индексах.

Из-за этого значение аргумента шаблона std::get должно поступать откуда-то, что известно компилятору во время компиляции. Переменная constexpr - это то, что имеет значение, известное компилятору, а также, например, переменные const, инициализированные с помощью константных выражений. Но простая неконстантная целочисленная переменная, даже если она инициализируется при определении, не имеет значения.

...