С++ 03. Проверяйте rvalue-vs-lvalue во время компиляции, а не только во время выполнения

В C++03 Foreach Boost, используя этот интересный метод, может обнаруживать во время выполнения независимо от того, является ли выражение lvalue или rvalue. (Я обнаружил это с помощью этого вопроса StackOverflow: Rvalues ​​в C++03)

Вот демонстрация работы во время выполнения

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

Теперь, когда я изложил вопрос, проверяя rvalue-ness в C++03 во время компиляции, я немного расскажу о том, что я пробовал до сих пор.

Я хочу иметь возможность выполнять эту проверку во время во время компиляции. Это просто в С++ 11, но мне интересно узнать о С++ 03.

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

true ? rvalue_probe() : EXPRESSION;

Это «истина» слева от ?, и поэтому мы можем быть уверены, что EXPRESSION никогда не будет оцениваться. Но интересно то, что оператор ?: ведет себя по-разному в зависимости от того, являются ли его параметры значениями lvalue или rvalue (щелкните ссылку выше, чтобы узнать подробности). В частности, он преобразует наш объект rvalue_probe одним из двух способов, в зависимости от того, является ли EXPRESSION lvalue или нет:

struct rvalue_probe
{
    template< class R > operator       R () { throw "rvalue"; }
    template< class L > operator       L & () const { throw "lvalue"; }
    template< class L > operator const L & () const { throw "const lvalue"; }
};

Это работает во время выполнения, потому что сброшенный текст можно перехватить и использовать для анализа того, было ли ВЫРАЖЕНИЕ lvalue или rvalue. Но я хочу каким-то образом определить во время компиляции, какое преобразование используется.

Теперь это потенциально полезно, потому что это означает, что вместо того, чтобы спрашивать

Является ли EXPRESSION rvalue?

мы можем спросить:

Когда компилятор компилирует true ? rvalue_probe() : ВЫРАЖЕНИЕ, какой из двух перегруженных операторов, operator X или operator X&, выбран?

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

Я думал, что смогу использовать что-то вроде

is_reference< typeof (true ? rvalue_probe() : EXPRESSION) > :: type

Если EXPRESSION является lvalue, то выбирается operator&, и я надеялся, что тогда все выражение будет иметь тип &. Но, похоже, это не работает. Типы ref и не-ref довольно сложно (невозможно?) различить, особенно сейчас, когда я пытаюсь копаться внутри выражения ?:, чтобы увидеть, какое преобразование было выбрано.

Вот демо-код, вставленный сюда:

#include <iostream>
using namespace std;
struct X {
        X(){}
};

X x;
X & xr = x;
const X xc;

      X   foo()  { return x; }
const X   fooc() { return x; }
      X & foor()  { return x; }
const X & foorc() { return x; }

struct rvalue_probe
{
        template< class R > operator       R () { throw "rvalue"; }
        // template< class R > operator R const () { throw "const rvalue"; } // doesn't work, don't know why
        template< class L > operator       L & () const { throw "lvalue"; }
        template< class L > operator const L & () const { throw "const lvalue"; }
};

typedef int lvalue_flag[1];
typedef int rvalue_flag[2];
template <typename T> struct isref     { static const int value = 0; typedef lvalue_flag type; };
template <typename T> struct isref<T&> { static const int value = 1; typedef rvalue_flag type; };

int main() {
        try{ true ? rvalue_probe() : x;       } catch (const char * result) { cout << result << endl; } // Y lvalue
        try{ true ? rvalue_probe() : xc;      } catch (const char * result) { cout << result << endl; } // Y const lvalue
        try{ true ? rvalue_probe() : xr;      } catch (const char * result) { cout << result << endl; } // Y       lvalue
        try{ true ? rvalue_probe() : foo();   } catch (const char * result) { cout << result << endl; } // Y rvalue
        try{ true ? rvalue_probe() : fooc();  } catch (const char * result) { cout << result << endl; } // Y rvalue
        try{ true ? rvalue_probe() : foor();  } catch (const char * result) { cout << result << endl; } // Y lvalue
        try{ true ? rvalue_probe() : foorc(); } catch (const char * result) { cout << result << endl; } // Y const lvalue

}

(В конце у меня был еще какой-то код, но он просто сбивает с толку. Вы действительно не хотите видеть мои неудачные попытки ответа! Приведенный выше код демонстрирует, как он может тестировать lvalue-versus-rvalue во время выполнения. .)


person Aaron McDaid    schedule 31.01.2012    source источник
comment
@hvd, я соответственно обновил конец вопроса. Я должен был сказать fooref, а не foo. Но в любом случае, typeof по-прежнему считает, что true ?: fooref() : fooref() не является ссылкой.   -  person Aaron McDaid    schedule 31.01.2012
comment
@hvd, ... Я также проверил typeof(x) и typeof(xr), и они дают один и тот же тип, для меня это не имеет смысла. В typeof есть несоответствия. Я получаю правильное поведение с typeof(int&), но это не то же самое с typeof(xr).   -  person Aaron McDaid    schedule 31.01.2012
comment
Тип и lvalue-ness x и xr одинаковы, поэтому я предполагаю, что результат typeof одинаков. Существует немного другое поведение с decltype, которое может вам помочь, но ваша версия g++ не поддерживает его.   -  person    schedule 31.01.2012
comment
Вы пытаетесь придумать стандарт is_lvalue_reference/is_rvalue_reference? Или я что-то упускаю.   -  person Andrew    schedule 31.01.2012
comment
@Andrew Теперь, когда я вижу ваше сообщение, я думаю, что проблема в том, что x - это lvalue, а x - это ссылка, это два разных вопроса, и они запутались в вопросе.   -  person    schedule 01.02.2012
comment
@ Эндрю, is_lvalue_reference - это функция С++ 11. Мне интересно, как это сделать в С++ 03. C++03 имеет lvalue и rvalue, и есть этот ?:, который может их различать. Я просто хочу иметь возможность сделать этот дополнительный шаг и обнаружить это во время выполнения.   -  person Aaron McDaid    schedule 01.02.2012
comment
@hvd, я не путаю проблему ссылок с lvalue-rvalue-ness. FOREACH в Boost показывает, как в C++03 существует способ проверить, является ли заданное выражение rvalue, проверив, имеет ли другое основанное на нем выражение значение < i>тип ссылки. Это удивительная техника, но это не путаница с моей стороны. Я не могу заставить его работать полностью. И я намеренно проверяю каждую версию каждого выражения, которое могу, потому что я хочу быть уверенным, что функция, которая возвращает &, обрабатывается как lvalue - (постоянная или неконстантная, ссылка или не ссылка, возврат из -функция против именованной локальной переменной)   -  person Aaron McDaid    schedule 01.02.2012
comment
@AaronMcDaid Теперь ты потерял меня. x - это lvalue, оно обрабатывается как rvalue, и ваш комментарий утверждает, что поведение для x является правильным. Почему же тогда это правильно?   -  person    schedule 01.02.2012
comment
Ты смущен. Выражения не могут иметь ссылочные типы. Они всегда либо типа void, либо типа объекта или функции.   -  person Johannes Schaub - litb    schedule 01.02.2012
comment
@JohannesSchaub-litb Да, возможно, это более понятный способ объяснения. Это означает, что xr как выражение является выражением lvalue, которое имеет тип X. x как выражение также является выражением lvalue типа X.   -  person    schedule 01.02.2012
comment
В ПОРЯДКЕ. Давайте сделаем шаг назад. Я рад удалить большую часть своего вопроса и оставить необработанный вопрос: как мы воспроизводим во время компиляции, функциональность, которую FOREACH Boost может так хорошо выполнять во время выполнения?. Страница, на которую я ссылаюсь, чтобы прояснить, что основной вопрос касается lvalue-vs-rvalue i C++03.   -  person Aaron McDaid    schedule 01.02.2012
comment
@AaronMcDaid В таком случае не работает ли обычный трюк char (&helper(...))[1]; / template <typename T> char (&helper(T&))[2]; / #define is_lvalue(x) (sizeof(helper(x)) == 2)?   -  person    schedule 01.02.2012
comment
Я изменил конец вопроса. Код, который у меня был в конце, не имел значения. Я понимаю комментарии, но это не помогает мне приблизиться к окончательному вопросу. @JohannesSchaub-litb, «выражение» (это правильное слово?) имеет «категорию значений» (опять же, это правильное слово?). Если вы перейдете по ссылке на страницу Boost, вы ясно увидите, как lvalue-or-rvalue-ness выражения связан с тем, вызывается ли operator X или operator X& в качестве преобразователя. Это то, что я пытаюсь использовать во время компиляции.   -  person Aaron McDaid    schedule 01.02.2012
comment
@hvd, я только что понял, что «эталонность» этого типа немного упускает из виду. Вопрос, который я должен был задать ближе к концу моего вопроса: Как мы определяем во время компиляции, какой перегруженный оператор преобразования (operator X или operator X&) был выбран?. Я обновлю свой вопрос.   -  person Aaron McDaid    schedule 01.02.2012
comment
@ Аарон, верно, но то, вызывается ли одно из них, не имеет ничего общего с типом выражения.   -  person Johannes Schaub - litb    schedule 01.02.2012
comment
@AaronMcDaid Какой из них вызывается, зависит исключительно от lvalue-ness невычисленного выражения, поэтому, если у вас уже есть тест (см. мой предыдущий комментарий), который проверяет lvalue-ness другим способом, что вам нужно помимо этого? Вы можете написать is_lvalue(true ? rvalue_probe() : x), что даст вам тот же результат, что и is_lvalue(x). Я вполне могу что-то упустить, но было бы полезно, если бы вы могли уточнить, что это за что-то.   -  person    schedule 01.02.2012
comment
@hvd, когда я смотрю на такие решения, я часто обнаруживаю, что они ломаются на const lvalues. const lvalue выглядит очень похоже на rvalue. Я поиграю с этим helper, о котором вы упомянули.   -  person Aaron McDaid    schedule 01.02.2012
comment
@hvd, этот помощник работает. Он получает неправильный ответ только в одном случае, он думает, что const X fooc(); является lvalue. Но довольно странно иметь const в таком возвращаемом значении, так что это не проблема. Но если все так просто, то почему FOREACH Boost доставил столько хлопот? :-) Может быть, я упускаю что-то более очевидное :-)   -  person Aaron McDaid    schedule 01.02.2012
comment
@AaronMcDaid Фу, ты прав. Значение const X является допустимым аргументом для параметра функции const X &, и это используемая перегрузка. Подожди...   -  person    schedule 01.02.2012
comment
@Xeo Я вижу T& с аргументом rvalue типа int, выведенным в T = int, что делает его ошибкой. Я подозреваю, что вы лучше меня знаете, правильно ли это.   -  person    schedule 01.02.2012
comment
@hvd: Неважно, я как-то сделал неправильное предположение, что T&, переданное rvalue типа X, выведет X const&. Это не так, поэтому мои предыдущие комментарии не применимы. :)   -  person Xeo    schedule 01.02.2012


Ответы (2)


Это потребовало некоторых усилий, но вот проверенный и работающий макрос is_lvalue, который правильно обрабатывает типы возвращаемых функций const struct S. Он основан на const struct S значениях r, не связанных с const volatile struct S&, в то время как const struct S значениях l делают это.

#include <cassert>

template <typename T>
struct nondeducible
{
  typedef T type;
};

char (& is_lvalue_helper(...))[1];

template <typename T>
char (& is_lvalue_helper(T&, typename nondeducible<const volatile T&>::type))[2];

#define is_lvalue(x) (sizeof(is_lvalue_helper((x),(x))) == 2)

struct S
{
  int i;
};

template <typename T>
void test_()
{
  T a = {0};
  T& b = a;
  T (* c)() = 0;
  T& (* d)() = 0;
  assert (is_lvalue(a));
  assert (is_lvalue(b));
  assert (!is_lvalue(c()));
  assert (is_lvalue(d()));
}

template <typename T>
void test()
{
  test_<T>();
  test_<const T>();
  test_<volatile T>();
  test_<const volatile T>();
}

int main()
{
  test<int>();
  test<S>();
}

Изменить: ненужный дополнительный параметр удален, спасибо, Xeo.

Изменить еще раз: судя по комментариям, это работает с GCC, но зависит от неуказанного поведения в C++03 (действительно для C++11) и не работает в некоторых других компиляторах. Восстановлен лишний параметр, что позволяет работать в большем количестве случаев. const class rvalue дает серьезную ошибку на некоторых компиляторах и дает правильный результат (false) на других.

person Community    schedule 31.01.2012
comment
Не обращайте внимания на мой последний комментарий, Clang по какой-то причине извергает много ошибок с этим... Clang также выдает ошибки в вашей предыдущей версии, с полной ошибкой, показанной здесь. Однако это может быть ошибка Clang. - person Xeo; 01.02.2012
comment
Хорошо, все Comeau, gcc и clang также принимают Lvalue функции с версией Xeo. Этот лязг и комо отклоняют этот код, потому что это уточнение C++11. Они уточнили, что только константные неизменяемые ссылки могут связываться со значениями rvalue. С++ 03 только что сказал, что привязка ссылки к неконстантному значению к rvalue невозможна. C++11 добавил энергонезависимую часть. То, что они по-прежнему отвергают его в C++03, связано с тем, что сама привязка ссылки по-прежнему имеет неправильный формат, даже если само разрешение перегрузки принимает преобразование. - person Johannes Schaub - litb; 01.02.2012
comment
Почему это работает? Это потому, что значения r не должны иметь никаких других ссылок на них и, следовательно, менее «изменчивы», чем значения l? Раньше я никогда не обращал внимания на volatile — спасибо, что побудили меня прочитать об этом! - person Aaron McDaid; 01.02.2012
comment
@JohannesSchaub-litb Это уточнение или изменение поведения? Если этот код работает с GCC в режиме C++03, потому что он реализует семантику C++11, меня это не устраивает :) - person ; 01.02.2012
comment
@Johannes: Can не означает должен, поэтому я думаю, что это правильно, если только в каком-то другом абзаце не сказано, что это не так. Кроме того, в проблеме cv ref в С++ 03: почему привязка все еще плохо сформирована? - person Xeo; 01.02.2012
comment
@AaronMcDaid Это потому, что функцию с параметром типа const volatile S& нельзя вызвать с аргументом const S rvalue. Существует специальное исключение, которое разрешает это с const S&, но это исключение не применяется к const volatile S&. (По крайней мере, так обстоит дело с GCC. Вопрос о том, верно ли это для C++03, все еще остается открытым.) - person ; 01.02.2012
comment
@hvd Я думаю, что воспринял бы это скорее как разъяснение, но мне так грустно, что лязг и комо онлайн отвергают это! И, пожалуйста, не обращайте внимания на мои комментарии о функциях lvalue. Я думаю, что это работает нормально с ними. - person Johannes Schaub - litb; 01.02.2012
comment
@Xeo да, я думаю, что он сравнивает T const volatile с void () (для lvalue функции void ()), чтобы найти совпадение. Теперь T const volatile имеет квалификацию const и volatile, но void() не является ни одним из них, поэтому они не совпадают. Я предполагаю, что теперь вопрос заключается в том, преобразовать ли T const volatile в T и снова сравнить и преуспеть, или попытаться преобразовать void() в const volatile identity‹void()›::type, и быть на месте, получая void( ) снова и снова не совпадают. По-видимому, существующие компиляторы выбирают первый путь и успешно выводят T. - person Johannes Schaub - litb; 01.02.2012
comment
3.9.3 определяет cv-qualified только для типов объектов и void (что может служить источником для устранения неоднозначности выбора выше). Таким образом, импл, отвергающий функцию lvalues, показался бы мне вполне разумным, в конце концов, выведенное A все еще не является более cv-квалифицированным, чем исходное преобразованное A. - person Johannes Schaub - litb; 01.02.2012
comment
@JohannesSchaub-litb Спасибо, но я запутался: [over.match.viable] говорит, что если параметр имеет ссылочный тип, последовательность неявного преобразования включает операцию привязки ссылки, поэтому, если привязка недействительна, не следует снять перегрузку? Или я читаю более поздний вариант? - person ; 01.02.2012
comment
@Johannes: Вернемся к сообщению об ошибке clang из предыдущей версии: вы сказали, что привязка ссылки по-прежнему неверна. сформированный по какой-то причине в C++03. Почему? - person Xeo; 01.02.2012
comment
@hvd, что последовательность неявного преобразования включает операцию привязки ссылки, означает, что X& не может привязываться к rvalue. И что X& предпочтительнее, чем X const&, если оба работают (это не преобразование квалификации, а особый случай!). Это не относится к фактической инициализации параметра, которая происходит после выбора функции разрешения перегрузки. Это, так сказать, эталонная привязка разрешения перегрузки. - person Johannes Schaub - litb; 01.02.2012
comment
@Johannes: Ах, так это ошибка, если читать одним способом, и соответствует, если читать другим способом в C++ 03? - person Xeo; 01.02.2012
comment
@JohannesSchaub-litb [over.ics.ref], похоже, не отвечает на него в любом случае: сначала говорится, что привязка неконстантной ссылки к временной недопустима, затем говорится, что другие ограничения, не основанные на типе ссылка и аргумент не проверяются. Но это еще одно ограничение, находящееся на основе типа ссылки и аргумента. - person ; 01.02.2012
comment
@Johannes: я думаю, что это может потребовать отчета о дефектах в соответствии со стандартом C ++. Это либо необходимо уточнить, либо правила ICS разрешения перегрузки необходимо скорректировать, чтобы они соответствовали обычным правилам привязки ссылок. Мнения? - person Xeo; 01.02.2012
comment
@Xeo C++11 ясно дает понять этот вопрос: open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1152 - person ; 01.02.2012
comment
@hvd: А, хорошо. Думаю, теперь я понял... Привязка rvalue к ref-to-cv совершенно неправильно сформирована в С++ 11 и должна исключать перегрузку из набора совпадений (здесь Clang глючит). В С++ 03 это нечетко и неясно. Правильный? - person Xeo; 01.02.2012
comment
@Xeo Да, в значительной степени вам не разрешено привязывать rvalue к ref-to-cv ни в C++11, ни в C++03, но C++11 фактически говорит, что это приводит к исключению перегрузок, и С++ 03 ничего не говорит об этой последней части. - person ; 01.02.2012
comment
Привет, ребята, я читал эти комментарии, но в основном они слишком сложные для меня :-) В любом случае спасибо, я думаю, что кое-что понял. В любом случае, в чем суть? Что может быть компилятор c++03, который связывает const volatile & с rvalue, и что они могут почти утверждать, что они являются соответствующим компилятором? И С++ 11 более понятен по теме - что такое поведение просто не разрешено? - person Aaron McDaid; 01.02.2012
comment
@hvd нет. существование последовательности преобразования не зависит от того, правильно ли сформирована инициализация переменной. Разрешение перегрузки нетерпеливо, оно допускает преобразование аргументов в параметры, даже если инициализация параметра позже будет неправильно сформирована. Например, нарушения прав доступа (приватный базовый класс) и эта самая проблема инициализации ссылок. Упомянутое вами несоответствие было исправлено в С++ 11, но, тем не менее, несоответствие не обязательно является противоречием. - person Johannes Schaub - litb; 01.02.2012
comment
@JohannesSchaub-litb Я не утверждал обратного; стандарт специально рассматривает некоторые случаи, когда разрешение перегрузки не отбрасывает функцию, даже если она не может быть вызвана. Я имею в виду, что описание связывания ссылок ясно в отношении того, что включено, а что нет, за исключением одного крайнего случая, который включается некоторыми компиляторами (соответствующими C++11) и исключается другими. - person ; 01.02.2012

Оператор адреса (&) можно использовать только с lvalue. Поэтому, если вы использовали его в тесте SFINAE, вы могли отличить его во время компиляции.

Статическое утверждение может выглядеть так:

#define STATIC_ASSERT_IS_LVALUE(x) ( (sizeof &(x)), (x) )

Версия признака может быть:

template<typename T>
struct has_lvalue_subscript
{
    typedef char yes[1];
    typedef char no[2];

    yes fn( char (*)[sizeof (&(((T*)0)->operator[](0))] );
    no fn(...);
    enum { value = sizeof(fn(0)) == 1 };
};

и может использоваться как

has_lvalue_subscript< std::vector<int> >::value

(Предупреждение: не проверено)

Я не могу придумать никакого способа проверить произвольное выражение, допустимое в контексте вызывающего, без нарушения компиляции в случае сбоя.

person Ben Voigt    schedule 31.01.2012
comment
Нельзя ли использовать оператор address-of с rvalue типа класса, у которых есть собственная функция operator&? - person ; 01.02.2012
comment
Мне это нравится. Это еще не полное решение, но оно правильное. Я сейчас немного экспериментирую с этим. - person Aaron McDaid; 01.02.2012
comment
@hvd: трудно сказать, какой результат вы хотите получить от класса, достаточно странного, чтобы перегрузить operator&. Вероятно, он пытается прозрачно обернуть lvalue, и в этом случае, возможно, этот тест должен сказать, что это lvalue. - person Ben Voigt; 01.02.2012
comment
Я думаю, я предполагаю иметь макрос с именем STATIC_ASSERT_IS_LVALUE(x), который вызовет сбой компиляции, если x не является lvalue (и который также будет оцениваться как x). Взять & было бы достаточно для меня. Если мы сможем просто написать утверждение, то, думаю, я соглашусь с этим. Как насчет использования оператора запятой? #define STATIC_ASSERT_IS_LVALUE(x) ( (typeof(&x) ) 0 , x) - person Aaron McDaid; 01.02.2012
comment
... в конечном счете, было бы неплохо, если бы он делал что-то кроме ошибки компиляции. Например, кому-то может понадобиться STATIC_ASSERT_IS_RVALUE вместо этого. Но мне и первого было бы достаточно. - person Aaron McDaid; 01.02.2012
comment
@AaronMcDaid Это проще: reinterpret_cast<const volatile char&>((x)) - это серьезная ошибка, если x не является lvalue - это работает с типами возвращаемых функций const T. - person ; 01.02.2012
comment
@AaronMcDaid Конечно, вы можете обернуть это в sizeof, чтобы получить невычисленное выражение. - person ; 01.02.2012
comment
@AaronMcDaid И под «работает» я имею в виду, генерирует ли предполагаемую ошибку - person ; 01.02.2012
comment
Определенно предпочтительнее sizeof, чем typeof, так как sizeof является стандартным. - person Ben Voigt; 01.02.2012