Аналог "discarding _" для С++
От: Went  
Дата: 29.06.25 17:42
Оценка:
Здравствуйте. В C# есть "discarding _", который по сути создаёт временный объект и возвращает для него ref, чтобы принимающая функция, кортеж или выражение могли благополучно в него написать и забыть. Как получить подобное поведение в С++? То есть чтобы некий довольно краткий синтаксис мог быть передан в любую функцию вместо параметра, принимающего не-константную ссылку? Например?
// Что нужно написать вместо ..., чтобы работало нижнее?
void foo(int&);
foo(...);
... = 1;
[x, ...] = get_some_struct();

Очевидно, что напрашивается функция, которая возвращает статическую переменную, объявленную внутри себя. Но в этом случае у нас получится один член на все вызовы, что чревато неожиданным поведением. Также не хотелось бы явно указывать тип аргумента, то есть discard() без <int> в конце, но это не обязательно. Также не хочется ничего на куче создавать без необходимости. Ну и ограничение на стандарт — С++14. Какие могут быть варианты?
Re: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 29.06.25 17:56
Оценка:
Здравствуйте, Went, Вы писали:

W>Здравствуйте. В C# есть "discarding _", который по сути создаёт временный объект и возвращает для него ref, чтобы принимающая функция, кортеж или выражение могли благополучно в него написать и забыть. Как получить подобное поведение в С++? То есть чтобы некий довольно краткий синтаксис мог быть передан в любую функцию вместо параметра, принимающего не-константную ссылку? Например?

W>
W>// Что нужно написать вместо ..., чтобы работало нижнее?
W>void foo(int&);
W>foo(...);
W>... = 1;
W>[x, ...] = get_some_struct();
W>

W>Очевидно, что напрашивается функция, которая возвращает статическую переменную, объявленную внутри себя. Но в этом случае у нас получится один член на все вызовы, что чревато неожиданным поведением. Также не хотелось бы явно указывать тип аргумента, то есть discard() без <int> в конце, но это не обязательно. Также не хочется ничего на куче создавать без необходимости.

W>Ну и ограничение на стандарт — С++14. Какие могут быть варианты?


Ну как раз вот это и хреново, поскольку самым нормальным решением был бы переход от конкретных типов к концептам.

Ну а с озвученными ограничениями:

template <typename T>
T& lvalue(T&& t) {
  return t; // На C++23 не проканает - нужно будет использовать forward<T&>(t)
}

foo(lvalue(42));
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 29.06.2025 17:56 rg45 . Предыдущая версия .
Re: Аналог "discarding _" для С++
От: kov_serg Россия  
Дата: 29.06.25 18:11
Оценка:
Здравствуйте, Went, Вы писали:

W>
W>// Что нужно написать вместо ..., чтобы работало нижнее?
W>void foo(int&);
W>foo(...);
W>... = 1;
W>[x, ...] = get_some_struct();
W>


А зачем весь этот мазахизм?
Что мешает просто написать:
void foo_int(int x) { foo(x); }
int get_some_struct_x() { struct some_struct s=get_some_struct(); return s.x; }
Re[2]: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 29.06.25 18:17
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>А зачем весь этот мазахизм?

_>Что мешает просто написать:
_>
_>void foo_int(int x) { foo(x); }
_>int get_some_struct_x() { struct some_struct s=get_some_struct(); return s.x; }
_>


Ну, очевидно необходимость повсеместно определять эти избыточные синтаксические сущности. Вот это настоящий мазохизм.
--
Справедливость выше закона. А человечность выше справедливости.
Re: Аналог "discarding _" для С++
От: A.J. Россия CintaNotes
Дата: 29.06.25 18:34
Оценка:
Здравствуйте, Went, Вы писали:

W>
W>[x, ...] = get_some_struct();
W>


Для случая с распаковкой get_some_struct можно использовать std::ignore
Re[2]: Аналог "discarding _" для С++
От: Went  
Дата: 30.06.25 06:18
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну как раз вот это и хреново, поскольку самым нормальным решением был бы переход от конкретных типов к концептам.

R>Ну а с озвученными ограничениями:

R>
R>template <typename T>
R>T& lvalue(T&& t) {
R>  return t; // На C++23 не проканает - нужно будет использовать forward<T&>(t)
R>}

R>foo(lvalue(42));
R>


Ничего не понял. У меня задача не получить lvalue из чего угодно, а сгенерировать временный lvalue-объект, уникальный для каждого вызова. То есть, чтобы было что-то наподобие
foo(discard<int>())

Но в идеале без <int> (хотя это вряд ли достижимо без граблей) и с минимальным оверхедом по синтаксису и производительности.
Re[2]: Аналог "discarding _" для С++
От: Went  
Дата: 30.06.25 06:24
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>А зачем весь этот мазахизм?

_>Что мешает просто написать:
_>
_>void foo_int(int x) { foo(x); }
_>int get_some_struct_x() { struct some_struct s=get_some_struct(); return s.x; }
_>


Аналогично, не понимаю при чем тут это. У меня задача — автоматизированная трансляция C# кода в С++. Хочу найти оптимальную замену "discarding _". Другими словами, во что транслировать такое:
void foo(out int x)
{
  x = 1;
}
foo(out var _);

Получаем такое:
void foo(int& x)
{
  x = 1;
}
foo(/*что сюда написать? чтобы универсально и портабельно?*/);
Re[3]: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 30.06.25 06:27
Оценка:
Здравствуйте, Went, Вы писали:

W>Ничего не понял. У меня задача не получить lvalue из чего угодно, а сгенерировать временный lvalue-объект, уникальный для каждого вызова. То есть, чтобы было что-то наподобие

W>
W>foo(discard<int>())
W>

W>Но в идеале без <int> (хотя это вряд ли достижимо без граблей) и с минимальным оверхедом по синтаксису и производительности.

Да я понял, что тебе нужно. Просто идеального дискардинга, как в C# не получается. Например, приведенный тобой вариант будет работать только для типов с дефолтным конструктором (в дополнение к необходимости явной спецификации типа). Вариант с lvalue просто позволяет сконструировать налету временный объект любого конструируемого типа и передать его в функцию по lvalue ссылке. Синтаксического сахара, конечно, меньше, чем в C#. Зато получается настоящий временный объект (не на куче и не в статике), который умрет в конце полного выражения или инициализатора. Это должно хорошо поддаваться оптимизации, поскольку компилятору прекрасно видно, что это объект "навыброс". Также охватываются типы, для которых отсутсвует возможность конструирования по дефолту (или для вызываемой функции не пофигу, с какими параметрами сконструирован объект).

P.S. А вообще аутпут параметрами лучше не злоупотреблять. С ними обязательно какой-нибудь геморрой — не такой, так эдакий.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 30.06.2025 7:03 rg45 . Предыдущая версия . Еще …
Отредактировано 30.06.2025 6:53 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:49 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:42 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:38 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:35 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:34 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:29 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 6:28 rg45 . Предыдущая версия .
Re[2]: Аналог "discarding _" для С++
От: Went  
Дата: 30.06.25 06:28
Оценка:
Здравствуйте, A.J., Вы писали:

AJ>Здравствуйте, Went, Вы писали:


W>>
W>>[x, ...] = get_some_struct();
W>>


AJ>Для случая с распаковкой get_some_struct можно использовать std::ignore

Да, но по ряду причин нужно универсальное решение, которое в частности должно передаваться в обычные функции.
Re[3]: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 30.06.25 06:32
Оценка:
Здравствуйте, Went, Вы писали:

W>>>
W>>>[x, ...] = get_some_struct();
W>>>


AJ>>Для случая с распаковкой get_some_struct можно использовать std::ignore

W>Да, но по ряду причин нужно универсальное решение, которое в частности должно передаваться в обычные функции.

А вот со structured bindings как раз всё просто, по-моему:

auto&& [x, _] = get_some_struct();


В более сложном случае:

auto&& [x, _0, _1, _2] = get_some_struct();
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 30.06.2025 6:33 rg45 . Предыдущая версия . Еще …
Отредактировано 30.06.2025 6:33 rg45 . Предыдущая версия .
Re[3]: Аналог "discarding _" для С++
От: kov_serg Россия  
Дата: 30.06.25 06:53
Оценка:
Здравствуйте, Went, Вы писали:

W>Аналогично, не понимаю при чем тут это. У меня задача — автоматизированная трансляция C# кода в С++. Хочу найти оптимальную замену "discarding _". Другими словами, во что транслировать такое:

void foo(out int x) { x = 1; }
..
foo(out var _);


foo(/*что сюда написать? чтобы универсально и портабельно?*/);


если есть foo(out var _); то добавить определение void foo(void);
void foo(int& x) { x=1; }
void foo(void) { int x; foo(x); }
...
foo();

или если не хочется вводить новые сигнатуры, воспользоваться анонимными функциями
([&]{ int x; foo(x); }());

или сделать костыль вида
template<class T>void call(void (*fn)(T&)) { T t; fn(t); }
...
call( foo );
будет напоминить fortran
Отредактировано 30.06.2025 7:01 kov_serg . Предыдущая версия .
Re[3]: Аналог "discarding _" для С++
От: so5team https://stiffstream.com
Дата: 30.06.25 07:07
Оценка: 4 (1) +1
Здравствуйте, Went, Вы писали:

W>Ничего не понял. У меня задача не получить lvalue из чего угодно, а сгенерировать временный lvalue-объект, уникальный для каждого вызова. То есть, чтобы было что-то наподобие

W>
W>foo(discard<int>())
W>

W>Но в идеале без <int> (хотя это вряд ли достижимо без граблей) и с минимальным оверхедом по синтаксису и производительности.

Самое простое, что сразу приходит в голову:
template<typename T>
struct discarding_arg
{
    T _tmp;

public:
    operator T&() &&
    {
        return _tmp;
    }
};

void f(int & i) {
    i = 0;
}

int main()
{
    f(discarding_arg<int>{});
}
Re[3]: Аналог "discarding _" для С++
От: so5team https://stiffstream.com
Дата: 30.06.25 07:11
Оценка: +1
Здравствуйте, Went, Вы писали:

W>Но в идеале без <int> (хотя это вряд ли достижимо без граблей) и с минимальным оверхедом по синтаксису и производительности.


Более продвинутый вариант без необходимости явно описывать тип:
template<typename T>
T &
get_temporary_ref()
{
    static thread_local T v;
    return v;
}

struct discarding_arg
{
public:
    template<typename T>
    operator T&() &&
    {
        return get_temporary_ref<T>();
    }
};

void f(int & i) {
    i = 0;
}

int main()
{
    f(discarding_arg{});
}

Понятное дело, что T должен быть default-constructible.
Re[4]: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 30.06.25 07:27
Оценка: 6 (1)
Здравствуйте, so5team, Вы писали:

S>Самое простое, что сразу приходит в голову:

S>
S>template<typename T>
S>struct discarding_arg
S>{
S>    T _tmp;

S>public:
S>    operator T&() &&
S>    {
S>        return _tmp;
S>    }
S>};

S>void f(int & i) {
S>    i = 0;
S>}

S>int main()
S>{
S>    f(discarding_arg<int>{});
S>}
S>



Как вариант:

template<typename T>
T& discarding_arg (T&& _tmp = {})
{
  return std::forward<T&>(_tmp); // До C++23 можно просто "return _tmp;"
}

void f(int & i) {
    i = 0;
}

int main()
{
    // Вариантов использования появляется чуть больше:
    f(discarding_arg<int>());
    f(discarding_arg(42));
    g(discarding_arg(std::vector{1, 2, 3}));
}


Только если присмотреться, окажется, что discarding_arg — это переименованный lvalue
Автор: rg45
Дата: 29.06.25
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 30.06.2025 7:53 rg45 . Предыдущая версия . Еще …
Отредактировано 30.06.2025 7:50 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 7:38 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 7:37 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 7:33 rg45 . Предыдущая версия .
Отредактировано 30.06.2025 7:27 rg45 . Предыдущая версия .
Re[4]: Аналог "discarding _" для С++
От: Went  
Дата: 01.07.25 06:05
Оценка:
Здравствуйте, so5team, Вы писали:

S>Самое простое, что сразу приходит в голову:

S>
S>template<typename T>
S>struct discarding_arg;
S>


Да, вот этот вариант я как раз и сделал в первую очередь. Но столкнулся с проблемой шаблонов — в таком случае выводится не T, а discarding_arg<T>, что не допустимо. Поэтому решил, что неявное приведение к T& лучше заменить явным вызовом:
foo(discard<int>().ref());

Многословно, но пока что ничего лучше в голову не пришло.
Re[5]: Аналог "discarding _" для С++
От: Went  
Дата: 01.07.25 06:08
Оценка:
Здравствуйте, rg45, Вы писали:

R>Как вариант:


R>
R>template<typename T>
R>T& discarding_arg (T&& _tmp = {})
R>{
R>  return std::forward<T&>(_tmp); // До C++23 можно просто "return _tmp;"
R>}

R>void f(int & i) {
R>    i = 0;
R>}

R>int main()
R>{
R>    // Вариантов использования появляется чуть больше:
R>    f(discarding_arg<int>());
R>    f(discarding_arg(42));
R>    g(discarding_arg(std::vector{1, 2, 3}));
R>}
R>


Кстати, да! Вот вариант с параметром по умолчанию мне в голову не пришел. Так получается короче
Re[4]: Аналог "discarding _" для С++
От: Went  
Дата: 01.07.25 06:09
Оценка:
Здравствуйте, so5team, Вы писали:
S>Более продвинутый вариант без необходимости явно описывать тип:
Да, но в этом случае у нас один экземпляр на все вызовы одного типа, если я правильно понял.
Re[5]: Аналог "discarding _" для С++
От: so5team https://stiffstream.com
Дата: 01.07.25 06:37
Оценка:
Здравствуйте, Went, Вы писали:

S>>Более продвинутый вариант без необходимости явно описывать тип:

W>Да, но в этом случае у нас один экземпляр на все вызовы одного типа, если я правильно понял.

Да. Но если

a) ссылки на это значение нигде не сохраняются;
b) значения просто сразу же выбрасываются,

то это самый дешевый способ.

Из вашего описания не очень понятно насколько вам важно вот такое:
void f(int & x, int & y) {
  x = 2;
  ...
  y = 3;
  ...
  x += y * x;
}

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

Если это критически важно, то да, именно этот подход не сработает.
Re[6]: Аналог "discarding _" для С++
От: Went  
Дата: 01.07.25 06:43
Оценка:
Здравствуйте, so5team, Вы писали:
S>Если это критически важно, то да, именно этот подход не сработает.
Я разрабатываю общее решение, и не могу заранее знать, как оно будет использоваться Если в шарпе это разные экземпляры, то и у меня должно быть так )
Re[7]: Аналог "discarding _" для С++
От: rg45 СССР  
Дата: 01.07.25 06:57
Оценка:
Здравствуйте, Went, Вы писали:

W>Я разрабатываю общее решение, и не могу заранее знать, как оно будет использоваться Если в шарпе это разные экземпляры, то и у меня должно быть так )


Ну вот, и тебя дотнет покусал. Редеют наши ряды.
--
Справедливость выше закона. А человечность выше справедливости.
Re[8]: Аналог "discarding _" для С++
От: Went  
Дата: 01.07.25 07:15
Оценка: :)
Здравствуйте, rg45, Вы писали:
R>Ну вот, и тебя дотнет покусал. Редеют наши ряды.
Ну, я пока что одной ногой еще здесь
Re[4]: Аналог "discarding _" для С++
От: _NN_  
Дата: 02.07.25 20:12
Оценка: 10 (1)
Здравствуйте, rg45, Вы писали:

R>В более сложном случае:


R>
R>auto&& [x, _0, _1, _2] = get_some_struct();
R>


C++26 позволяет использовать _ :

struct A{int i; int j;int k;};

A f() {
    return {1,2,3};
}

int main() {
    auto [x, _, _] = f();
}


https://www.sandordargo.com/blog/2025/01/08/cpp26-unnamed-placeholders
http://rsdn.nemerleweb.com
http://nemerleweb.com
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.