Как работает «контекстное преобразование» с операторами `&&` и `||` в сочетании с пользовательскими перегрузками операторов? - PullRequest
4 голосов
/ 25 сентября 2011

От превосходного c++-faq вопроса @ Xeo: Является ли идиома safe-bool устаревшей в C ++ 11? Я узнал, что идиома safe bool больше не нужна, потому что explicit user- определенное преобразование в bool будет автоматически вызываться в тех случаях, когда в C ++ 03 требовался безопасный bool.

Однако возможность перегрузки таких операторов, как &&, || и !, кажется, обходит это.

Случаи, когда operator! необходим помимо обеспечения преобразования в bool, являются редкими, как и operator&& и operator||, но реализации дерева выражений C ++ (используемые для методов отложенного выполнения и символьной математики) требуют переопределения это.

Происходит ли "контекстное преобразование", когда вызывается пользовательский оператор? Какой тип заклинания SFINAE необходим, чтобы убедиться, что определение operator&& или operator|| будет работать правильно как с типами, реализующими «безопасный bool», так и с типами, предназначенными для «контекстного преобразования»?


Для уточнения дано:

class uses_safe_bool
{
    void f() {};
    typedef void (uses_safe_bool::* safe_bool)();

public:
    operator safe_bool() const { return (rand() & 1)? &uses_safe_bool::f: 0; }
};

class uses_explicit_bool
{
public:
    explicit operator bool() const { return rand() & 1; }
};

template<typename T>
class deferred_expression
{
    // Not convertible to bool
public:
    T evaluate() const;
};

Какие подписи требуются для operator||, так что все следующие выражения являются действительными:

deferred_expression<bool> db;
uses_safe_bool sb;
uses_explicit_bool eb;
int i;

auto test1 = sb || db;
auto test2 = eb || db;
auto test3 = true || db;
auto test4 = false || db;
auto test5 = i || db;

они используют другую перегрузку:

auto test6 = db || db;

deferred_expression<int> di;
auto test7 = di || db;

и следующие отклоняются во время компиляции:

std::string s;
auto test7 = s || db;

std::vector<int> v;
auto test8 = v || db;

deferred_expression<std::string> ds;
auto test9 = ds || db;

1 Ответ

4 голосов
/ 25 сентября 2011

Правило одинаково для C ++ 03 (идиома safe-bool) и для C ++ 11 (оператор явного преобразования): не перегружайте логические операторы для этого (чтобы не потерять поведение короткого замыкания,плюс по умолчанию работают просто отлично).Последний будет работать, потому что операнды встроенных логических операторов имеют право на контекстное преобразование, например, для && из n3290, 5.14 Логический оператор AND [expr.log.and]:

1 Группы операторов && слева направо. Оба операнда контекстно преобразуются в тип bool (пункт 4) .

(выделено, похожий текст для других операторов)


перегруженные операторыявляются обычными вызовами функций, поэтому контекстное преобразование не выполняется.Убедитесь, что ваши перегруженные логические операторы всегда выбраны с разрешением перегрузки, и все готово.Например, это игнорирование lvalues:

struct evil {
    explicit operator bool() const;
};
void operator||(evil&&, evil&&);

evil e;
// built-in operator||
e || e;

// overloaded operator||
evil() || evil()

Обратите внимание, что template<typename Lhs, typename Rhs> void operator||(Lhs&&, Rhs&&); будет выбрано через ADL, когда любой из операндов имеет тип класса, независимо от cv -qualifiersи ценовой категории.

...