Почему std::string_view создает оборванное представление в троичном выражении?

Рассмотрим метод, возвращающий std::string_view либо из метода, возвращающего const std::string&, либо из пустой строки. К моему удивлению, написание метода таким образом приводит к просмотру висячей строки:

const std::string& otherMethod();

std::string_view myMethod(bool bla) {
    return bla ? otherMethod() : ""; // Dangling view!
}

https://godbolt.org/z/1Hu_p2

Кажется, что компилятор сначала помещает временную std::string копию результата otherMethod() в стек, а затем возвращает представление этой временной копии вместо того, чтобы просто возвращать представление ссылки. Сначала я подумал об ошибке компилятора, но и G++, и clang делают это.

Исправить это легко: обертывание otherMethod в явную конструкцию string_view решает проблему:

std::string_view myMethod(bool bla) {
    return bla ? std::string_view(otherMethod()) : ""; // Works as intended!
}

https://godbolt.org/z/Q-sEkr

Почему это так? Почему исходный код создает неявную копию без предупреждения?


person gexicide    schedule 17.06.2019    source источник


Ответы (1)


Потому что так работает условный оператор.

Вы вызываете ?: для двух операндов, один из которых является lvalue типа std::string const, а другой — lvalue типа char const[1]. Языковое правило для условного оператора... очень сложное. соответствующее правило:

В противном случае, если второй и третий операнд имеют разные типы и либо имеют тип класса (возможно, cv-qualified), либо если оба являются значениями gl одной и той же категории значений и одного типа, за исключением cv -квалификация, делается попытка сформировать последовательность неявного преобразования каждого из этих операндов в тип другого. [ Примечание: такие свойства, как доступ, является ли операнд битовым полем или удалена ли функция преобразования, игнорируются при таком определении. — конец примечания ] Предпринимаются попытки сформировать последовательность неявного преобразования из операндного выражения E1 типа T1 в целевой тип, связанный с типом T2 операндного выражения E2, следующим образом:

  • Если E2 является значением lvalue, целевой тип — это «ссылка lvalue на T2», с учетом ограничения, согласно которому при преобразовании ссылка должна напрямую связываться ([dcl.init.ref]) с значением gl.
  • Если E2 является значением x, [...]
  • Если E2 является значением prvalue или если ни одна из приведенных выше последовательностей преобразования не может быть сформирована и по крайней мере один из операндов имеет (возможно, cv-квалифицированный) тип класса:

    • if T1 and T2 are the same class type [...]
    • в противном случае, если T2 является базовым классом T1, [...]
    • в противном случае целевым типом будет тип, который E2 будет иметь после применения стандартных преобразований lvalue-to-rvalue, массива в указатель и функции в указатель.

С помощью этого процесса определяется, может ли быть сформирована последовательность неявного преобразования из второго операнда в целевой тип, определенный для третьего операнда, и наоборот. Если могут быть сформированы обе последовательности или может быть сформирована одна, но это неоднозначная последовательность преобразования, то программа сформирована неправильно. Если никакая последовательность преобразования не может быть сформирована, операнды остаются без изменений, и дальнейшая проверка выполняется, как описано ниже. В противном случае, если может быть сформирована ровно одна последовательность преобразования, это преобразование применяется к выбранному операнду, и преобразованный операнд используется вместо исходного операнда в оставшейся части этого подпункта. [ Примечание. Преобразование может быть некорректным, даже если может быть сформирована неявная последовательность преобразования. — конец примечания ]

Невозможно преобразовать std::string const ни в char const(&)[1], ни в char const*, но вы можете преобразовать char const[1] в std::string const (внутренний вложенный маркер)... вот что вы получите. Значение типа std::string const. То есть вы либо копируете одну строку, либо создаете новую... в любом случае вы возвращаете string_view во временное значение, которое сразу же выходит из области видимости.


То, что вы хотите, это либо то, что у вас было:

std::string_view myMethod(bool bla) {
    return bla ? std::string_view(otherMethod()) : "";
}

or:

std::string_view myMethod(bool bla) {
    return bla ? otherMethod() : ""sv;
}

Результатом этого условного оператора является string_view, причем оба преобразования безопасны.

person Barry    schedule 17.06.2019
comment
@Holt Ну, это условный оператор, а не тернарный оператор :-P - person Barry; 17.06.2019
comment
@Barry: Ternary - это не имя, а описательное прилагательное. - person Ben Voigt; 23.06.2019