Привязать временную к неконстантной ссылке - PullRequest
16 голосов
/ 02 февраля 2012

Обоснование

Я стараюсь избегать присвоений в коде C ++ полностью .То есть я использую только инициализации и объявляю локальные переменные как 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&);

То есть аргумент i non - const и, как следствие, я не могу связать его с временным,Похоже, что обойти это невозможно, используя отдельную переменную.

Ugly Workaround

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

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

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

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

Ответы [ 4 ]

14 голосов
/ 02 февраля 2012

Почему бы просто не перегрузить 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]);
}

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

11 голосов
/ 02 февраля 2012

Решение с 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;
    }
}

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

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

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

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

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

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 голосов
/ 02 февраля 2012

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

1 голос
/ 02 февраля 2012

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

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

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

...