пытаясь различать разные виды rvalue - литералы и нелитералы

Я хочу иметь метод, который можно вызывать только с lvalue, поэтому я сделал следующее:

template <typename T>
MyClass& cache(T&) {}

template <typename T>
MyClass& cache(const T&&) = delete;

И это прекрасно работает — я даже могу передать строковые литералы C, поскольку они также являются lvalues.

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

Работает следующий код:

MyClass a;
a.cache("string literal");

int temp = 6;
a.cache(temp);

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

int getInt(); // fwd decl
a.cache(getInt());

Но я также хочу иметь возможность передавать другие литералы, но все они кажутся rvalue...

Следующий код не работает (хотя хотелось бы):

MyClass a;
a.cache(6);

Есть ли способ отличить такие литералы от небуквенных rvalue?

Есть ли способ легко превратить такие литералы в lvalue? Я нашел этот ответ на переполнение стека, в котором упоминается что-то вроде unless you write 1L, но даже если я даю суффикс L литералу, это все равно временный.

Даже макрос в порядке - что-то вроде этого: a.cache(TURN_TO_LVALUE(6)); (или CONSTANTIZE(6))


person onqtam    schedule 01.05.2017    source источник
comment
что, если я передам ссылку на локальную переменную? Вам лучше задокументировать, что вы работаете должным образом.   -  person The Techel    schedule 01.05.2017
comment
В C++14: template <typename T, T N> constexpr T cliteral = N;, использование: a.cache(cliteral<int, 6>). В C++17: template <auto N> inline constexpr auto cliteral = N;, использование: a.cache(cliteral<6>).   -  person Kerrek SB    schedule 01.05.2017
comment
@TheTechel можно использовать локальные объекты - объекты MyClass создаются с помощью макроса как локальные и живут только до тех пор, пока не покинут свою область действия, поэтому кэширование других локальных объектов, созданных до них, нормально.   -  person onqtam    schedule 01.05.2017
comment
@KerrekSB, поэтому использование суффикса (как предложено в другом вопросе SO), такого как L, ничего не даст?   -  person onqtam    schedule 01.05.2017
comment
@onqtam: я до сих пор не могу сказать, 1 апреля ли это или это самый изощренный розыгрыш, но идея суффикса L, создающего lvalue, — самая веселая вещь, которую я слышал за всю неделю :-)   -  person Kerrek SB    schedule 01.05.2017
comment
@KerrekSB Так что я также буду использовать такой макрос: #define LITERAL_TO_LVALUE(x) cliteral<decltype(x), x>. Вы можете оставить свой комментарий в качестве ответа, и я приму его   -  person onqtam    schedule 01.05.2017
comment
Как вы думаете, почему можно кэшировать указатель на аргумент в a.cache(6);?   -  person aschepler    schedule 01.05.2017
comment
@aschepler А почему ты не думаешь, что это нормально? Я реализую функцию ведения журнала для своей среды тестирования (doctest) — см. здесь. Я кэширую указатели на объекты, и только если что-то в той же области выходит из строя, я использую эти указатели для строковой обработки объектов, на которые они указывают, и ленивого построения сообщения. Если вы видите проблему с этим дизайном, я буду рад получить обратную связь :) Мне все равно не хватает экспертной оценки...   -  person onqtam    schedule 01.05.2017
comment
@onqtam Ни один указатель на 6 не является допустимым после окончания оператора, в котором находится 6. Поэтому кэширование указателя на 6 в a.cache(6) недопустимо.   -  person Yakk - Adam Nevraumont    schedule 01.05.2017
comment
@KerrekSB Я считаю, что 6_L может быть lvalue. Я мог бы сделать это более явным: 6_lvalue.   -  person Yakk - Adam Nevraumont    schedule 01.05.2017
comment
@Yakk: даже нельзя говорить об указателе на 6. Такой указатель не может существовать, потому что 6 является значением prvalue. Вы всегда сначала инициализируете объект из этого значения prvalue, а затем можете взять адрес этого объекта. Сам литерал не является объектом. C++17 делает это немного более очевидным благодаря улучшенным категориям значений, но в основном так оно и работало всегда.   -  person Kerrek SB    schedule 01.05.2017
comment
@KerrekSB Да, я был гораздо осторожнее в формулировках в своем ответе ниже. :) Полное различение временного экземпляра, созданного при определенных обстоятельствах, от буквального 6 от самого буквального 6 съело бы половину символов, разрешенных в комментарии. @_@   -  person Yakk - Adam Nevraumont    schedule 01.05.2017


Ответы (1)


Вас может смутить "hello"; это литерал, но (в некотором смысле) также lvalue (если константа). "hello" создает объект, который не исчезает после окончания строки, массив константных символов, состоящий из {'h', 'e', 'l', 'l', 'o', '\0'}. Два разных "hello" могут относиться к одному и тому же объекту или нет.

6 не делает то же самое; в программе на C++ нет постоянной 6 с константой 6 в ней, есть постоянная "hello" в программе на C++ со строковой константой "hello" в ней.

Литерал 6 может привести к созданию экземпляра временного объекта. Время жизни этого временного объекта - до конца выражения, в котором оно находится ("конец строки", где оно находится).

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

Указатель на этот временный объект будет недействительным в этот момент; даже == или < для этого указателя является неопределенным поведением.

Итак, a.cache(6) — плохой план.

Теперь мы можем сделать что-то ужасное.

unsigned long long const& operator""_lvalue(unsigned long long x) {
    thread_local unsigned long long value;
    value = x;
    return value;
}

живой пример.

Это создает статическое значение длительности хранения lvalue value и копирует в него x. Таким образом, 6_lvalue + 4_lvalue будет либо 8, либо 12, а не 10, так как единственное lvalue будет перезаписано.

Мы можем решить эту проблему перезаписи, увеличив злоупотребление шаблонами.

template<int P>
constexpr unsigned long long pow_( unsigned x, std::size_t tens ) {
  if (tens == 0) return x;
  return P*pow_<P>(x, tens-1);
}
template<int base>
constexpr unsigned long long ucalc(std::integer_sequence<char>) {
  return 0;
}
constexpr unsigned digit( char c ) {
    if (c >= '0' && c <= '9') return c-'0';
    if (c >= 'a' && c <= 'z') return c-'a'+10;
    if (c >= 'A' && c <= 'Z') return c-'A'+10;
    exit(-1);
}
template<int base, char c0, char...chars>
constexpr unsigned long long ucalc(std::integer_sequence<char, c0, chars...>) {
  return pow_<base>( digit(c0), sizeof...(chars) ) + ucalc<base>( std::integer_sequence<char, chars...>{} );
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, chars...>) {
  return ucalc<10>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'x', chars...>) {
  return ucalc<16>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'X', chars...>) {
  return ucalc<16>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'b', chars...>) {
  return ucalc<2>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'B', chars...>) {
  return ucalc<2>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', chars...>) {
  return ucalc<8>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc() {
  return calc( std::integer_sequence<char, chars...>{} );
}
template<class T, T x>
constexpr T lvalue = x;
template <char... chars>
unsigned long long const& operator "" _lvalue() {
  return lvalue<unsigned long long, calc<chars...>()>;
}

живой пример. Примерно половина из них приходится на поддержку 0b, 0x и 0 двоичных/шестнадцатеричных/восьмеричных форматов.

Использовать:

a.cache(6_lvalue);

В качестве альтернативы в С++ 17 вы можете сделать это:

template<auto x>
constexpr auto lvalue = x;

or in C++14

template<class T, T x>
constexpr T lvalue = x;

с

#define LVALUE(...) lvalue<std::decay_t<decltype(__VA_ARGS__)>, __VA_ARGS__>

В С++ 17 это lvalue<7>, а в С++ 14 — LVALUE(7).

person Yakk - Adam Nevraumont    schedule 01.05.2017
comment
Шаблоны переменных не страдают от проблем ODR даже в С++ 14 (возможно, с extern, чтобы быть абсолютно уверенным, что он имеет внешнюю связь - это отдельная основная проблема). - person T.C.; 01.05.2017
comment
@Т.С. Я не так часто использую переменные шаблона; нет требования, чтобы вы создавали их где-то уникальным образом? У меня сложилось (очевидно ошибочное) впечатление, что именно поэтому мы добавили inline переменных. Или куда добавили нешаблонные переменные (например, нешаблонные переменные шаблонного класса)? - person Yakk - Adam Nevraumont; 01.05.2017
comment
inline предназначен в первую очередь для переменных, не являющихся шаблонами, и статических элементов данных. Связь между встроенными переменными и шаблонами переменных похожа на связь между встроенными функциями и шаблонами функций. - person T.C.; 01.05.2017