Привязать временную к неконстантной ссылке

Обоснование

Я пытаюсь полностью исключить назначения в С++-коде. То есть я использую только инициализации и объявляю локальные переменные как const, когда это возможно (т.е. Всегда, кроме переменных цикла или аккумуляторов).

Теперь я нашел случай, когда это не работает. Я считаю, что это общая картина, но, в частности, она возникает в следующей ситуации:

Описание проблемы

Позволяет сказать, что у меня есть программа, которая загружает содержимое входного файла в строку. Вы можете вызвать инструмент, указав имя файла (tool filename) или используя стандартный поток ввода (cat filename | tool). Теперь, как инициализировать строку?

Следующие действия не работают:

bool const use_stdin = argc == 1;
std::string const input = slurp(use_stdin ? static_cast<std::istream&>(std::cin)
                                          : std::ifstream(argv[1]));

Почему это не работает? Поскольку прототип slurp должен выглядеть следующим образом:

std::string slurp(std::istream&);

То есть аргумент я не const и, как следствие, я не могу привязать его к временному. Кажется, что это не так, используя отдельную переменную.

Ужасное обходное решение

В настоящее время я использую следующее решение:

std::string input;
if (use_stdin)
    input = slurp(std::cin);
else {
    std::ifstream in(argv[1]);
    input = slurp(in);
}

Но это тщетно меня неправильно. Прежде всего, это больше кода (в SLOC), но он также использует if вместо (здесь) более логического условного выражения и его назначение назначения после объявления, которое я хочу избежать.

Есть ли хороший способ избежать этого непрямого стиля инициализации? Проблема может быть обобщена во всех случаях, когда вам нужно мутировать временный объект. Arent-потоки, плохо разработанные для того, чтобы справляться с такими случаями (поток const не имеет смысла, и все же работа над временным потоком имеет смысл)?

Ответы

Ответ 1

Почему бы просто не перегрузить slurp?

std::string slurp(char const* filename) {
  std::ifstream in(filename);
  return slurp(in);
}

int main(int argc, char* argv[]) {
  bool const use_stdin = argc == 1;
  std::string const input = use_stdin ? slurp(std::cin) : slurp(argv[1]);
}

Это общее решение с условным оператором.

Ответ 2

Решение с if является более или менее стандартным решением, когда имея дело с argv:

if ( argc == 1 ) {
    process( std::cin );
} else {
    for ( int i = 1; i != argc; ++ i ) {
        std::ifstream in( argv[i] );
        if ( in.is_open() ) {
            process( in );
        } else {
            std::cerr << "cannot open " << argv[i] << std::endl;
    }
}

Однако это не относится к вашему делу, поскольку ваша главная задача заключается в том, чтобы получить строку, а не "обрабатывать" имена файлов args.

В моем собственном коде я использую MultiFileInputStream, который я написал, который принимает список имен файлов в конструкторе и возвращает EOF только тогда, когда последнее было прочитано: если список пуст, он читает std::cin. Эта обеспечивает элегантное и простое решение вашей проблемы:

MultiFileInputStream in(
        std::vector<std::string>( argv + 1, argv + argc ) );
std::string const input = slurp( in );

Этот класс стоит писать, поскольку он обычно полезен, если вы часто писать Unix-подобные служебные программы. Однако определенно не тривиально, и может быть много работы, если это одноразовая потребность.

Более общее решение основано на том, что вы можете назвать не-константной функции-члена на временной основе и тот факт, что большая часть члены функции std::istream возвращают a std::istream& — a non const-reference, который затем привязывается к неконтактной ссылке. Так вы всегда можете написать что-то вроде:

std::string const input = slurp(
            use_stdin
            ? std::cin.ignore( 0 )
            : std::ifstream( argv[1] ).ignore( 0 ) );

Я бы подумал, что это немного взломать, и он имеет более общий проблема в том, что вы не можете проверить, является ли открытый (вызываемый конструктором из std::ifstream.

В целом, хотя я понимаю, чего вы пытаетесь достичь, я подумайте, что вы обнаружите, что IO почти всегда будет представлять собой исключение. Вы не можете прочитать int, не указав его первым, и вы не можете прочитайте строку, не указав сначала std::string. согласен что это не так изящно, как могло бы быть, но тогда, код, который правильно обрабатывает ошибки редко столь же изящно, как хотелось бы. (Одно решение здесь нужно было бы вывести из std::ifstream, чтобы выбросить исключение, если открытый не работал; все, что вам нужно, это конструктор, который проверял is_open() в теле конструктора.)

Ответ 3

Все языки в стиле SSA должны быть полезными для реализаций phi-узлов. Вы столкнулись бы с одной и той же проблемой в любом случае, когда вам нужно построить два разных типа в зависимости от значения условия. Тройной оператор не может обрабатывать такие случаи. Конечно, в С++ 11 есть другие трюки, такие как перемещение потока или подобное, или использование лямбда, а дизайн IOstream - это фактически точная противоположность того, что вы пытаетесь сделать, поэтому, на мой взгляд, вы просто нужно сделать исключение.

Ответ 4

Другим вариантом может быть промежуточная переменная для хранения потока:

std::istream&& is = argc==1? std::move(cin) : std::ifstream(argv[1]);
std::string const input = slurp(is);

Воспользовавшись тем, что названные ссылки rvalue являются lvalues.