Void(), оператор запятой (оператор) и невозможная (?) Перегрузка

Рассмотрим следующую структуру:

struct S {};

В С++ 14 верно следующее определение:

constexpr auto f() { return S{}, 'c'; }

Кроме следующего:

constexpr auto f() { return S{}, void(); }

Теперь рассмотрим следующий рабочий фрагмент, который включает в себя первое из двух определений:

#include<type_traits>

struct S {};

constexpr int operator,(S, char) { return 42; }
constexpr auto f() { return S{}, 'c'; }

int main() {
    constexpr int i{f()};
    static_assert(i == 42, "!");
    static_assert(std::is_same<decltype(f()), int>::value, "!");
}

Говоря не так технически, перегрузка оператора запятой перехватывает пару S{}, 'c' и возвращает целое число, которое правильно проверено в функции main.

Теперь предположим, что я хочу сделать то же самое со вторым определением f:

constexpr auto f() { return S{}, void(); }

В этом случае оператор запятой должен перехватить форму S{}, void().
Ни одно из следующих определений не работает (по понятным причинам):

constexpr int operator,(S, void) { return 42; }

И ни один ниже (который бы работал в предыдущем случае):

template<typename T> constexpr int operator,(S, T &&) { return 42; }

Есть ли способ перегрузить оператор запятой, чтобы иметь дело с S{}, void()?
Разве это не недостаток в стандарте, так как он позволяет использовать оператор запятой таким образом, но не дает вам возможности перегружать тот же оператор (даже если стандартный что перегруженные функции с участием S разрешены)?


Примечание: этот вопрос сделан ради любопытства. Пожалуйста, избегайте комментариев, например, не делайте этого, или это не очень хорошая практика. Я не планирую делать это в производственных условиях. Спасибо.

Ответы

Ответ 1

Соответствующее предложение для этого - 13.3.1.2/9 [over.match.oper] в N4140:

Если оператор является оператором ,, унарный оператор & или оператор ->, и нет жизнеспособных функций, то оператор считается встроенным оператором и интерпретируется в соответствии с пунктом 5.

Поскольку void() никогда не является допустимым аргументом функции (см. 5.2.2/7 [expr.call]), никогда не будет жизнеспособной функции, и поэтому будет использоваться встроенный ,.

Так что нет, то, что вы пытаетесь сделать, невозможно.

Фактически, запись цикла итератора, подобного этому

for(...; ++it1, (void)++it2)

является стандартным способом предотвращения нарушения пользователем кода путем перегрузки , для их типов итераторов путем принудительного использования встроенного оператора ,. (Обратите внимание, что я не говорю, что вам нужно сделать это в своем повседневном коде. Это очень зависит от его фактического использования. Это стандартный уровень библиотеки параноидального кода.)


Относительно стандартного предложения, которое вы указали:

Значение операторов =, (унарный) &, и (comma), предопределенных для каждого типа, может быть изменено для определенных классов и типов перечисления путем определения операторных функций, реализующих эти операторы.

Но такая функция не может определяться, потому что, как я сказал выше, void() никогда не является допустимым аргументом функции.

В настоящее время обсуждается вопрос о том, является ли это надзором/проблемой в стандарте.