cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

c++ template deduction by requested returned type #371

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +3, -6 Автор идеи: Fihtangolz

Pixel **image = matrix_allocate(height, width) -> Pixel **image = matrix_allocate(height, width)

Вывод типа от запрашиваемого возврашаемого типа.


template<typename T>
T matrix_allocate(int height, int width){
    // some
}
apolukhin commented 3 years ago

Andrey Davydov, 30 октября 2018, 7:43 Для того чтобы это заработало должна существовать некоторая концепция expected type, а ее в C++ нет. Наверное, писав это преждложение Вы имели в виду, что-то вроде

Marix m = matrix_allocate(1024, 1024);

но в этом случае ничем не хуже использовать

auto m = matrix_allocate<Matrix>(1024, 1024);

В более же общей ситуации, когда вызов функции matrix_allocate является частью другого выражения, скажем foo(matrix_allocate(1024, 1024)), неизвестно аргумент какого типа ожидает foo (потому что перегрузок foo может быть много), более того мы даже не можем все перегрузки foo, не зная типы аргументов (ADL).

Fihtangolz, 31 октября 2018, 2:05 Andrey Davydov, для foo мне кажется стоит использовать принци "единственной точки зависимости" тоесть в данном случае требование должно быть разрешимо единственным образом и зависет от одной точки требования. Вообшем если мы запроси шаблонный параметр в качестве типа мы должны получит туже ошибку что сейчас и явно передать тип как шаблонный параметр если же функция предположим имеет статический тип int все хорошо или хорошо если Matrix b = foo(matrix_allocate(1024, 1024)); где T foo(T __s){ //... }. Ну вопрос тут скорей в следуюшем, почему бу и нет если это никому хуже не сделает? А писать так чаще удобней чес с auto

webreh, 31 октября 2018, 3:06 Andrey Davydov, ну вообще не особо нужна, результат достижим. Другое дело, что, видимо, нужно явно помечать "template ", иначе компилятору может быть не очень понятно, что надо действовать как ниже

#include <tuple>
#include <utility>

namespace details {
    template <typename Tuple>
    struct return_type_deductor { 
        static_assert(!std::is_reference_v<Tuple>);
        Tuple tuple_;

        return_type_deductor(Tuple&& tuple) : tuple_(std::forward<Tuple>(tuple)) {
        }

        template <typename T> struct tag {};

        template <typename T> operator T() && {
            return std::move(*this).details(tag<T>{}, std::make_index_sequence<std::tuple_size_v<Tuple>>{});
        }

        template <typename T, std::size_t... I>
        T details(tag<T>, std::index_sequence<I...>) && {
            return T{std::get<I>(std::move(tuple_))...};
        }
    };
}

template <typename... Args>
auto deduce_by_return_type(Args&&... args) {
    return details::return_type_deductor{std::make_tuple<Args...>(std::forward<Args>(args)...)};
}

auto matrix_allocate(int height, int width) { 
    return deduce_by_return_type(height, width); 
}

template <typename T>
T matrix_allocate(int height, int width) { 
    return deduce_by_return_type(height, width); 
}

struct matrix {
    int height, width;
};

void foo() {
    matrix a = matrix_allocate(12, 12);
    auto b = matrix_allocate<matrix>(12, 12);
}

webreh, 31 октября 2018, 3:34 Andrey Davydov, и да, ваш пример с auto немедленно ломается, когда deduction происходит по параметру функции.

matrix transpose(matrix x) {
    return { x.width, x.height } ;
}

void goo() {
    auto c = transpose(matrix_allocate(10, 12));
}

Fihtangolz, 31 октября 2018, 5:49 Andrey Davydov, вообщем константин красавец, реализация как сахар на cast вполне себе вкусная, он поскромничал дописать, что можно так:

template <typename T = deduce_by_return_type_functor>
T matrix_allocate(int height, int width) { 

    return T{height, width}; 
}

, вообщем ожидается что в стандарт попадет вот это вот как стандартное поведение для типа возврата, если не указанное любое другое значение по умолчанию

Andrey Davydov, 31 октября 2018, 9:15 webreh, то что Вы написали безусловно красивый трюк, хотя он и описывается двумя стандартными функциями std::forward_as_tuple, std::make_from_tuple (по модулю того, что там используется инициализация круглыми скобками, а у Вас фигурными, но неизвестно, что именно нужно пользователю), но при чем здесь вывод типа? Так, способ вернуть proxy. А если allocate состоит не из одного return statement, а должен сделать еще что-то полезное с возвращаемым значение (заполнить нулями, скажем)?

T allocate(int width, int height) {
  T result = ...;// construct somehow
  result.fill(0);
  return result;
}

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

struct square_matrix {};

square_matrix transpose(square_matrix x) {
    return {};
}

работать не будет.

webreh, 1 ноября 2018, 3:46 Andrey Davydov, сам чуть не написал через forward_as_tuple, но так нельзя, нельзя forward в выходящую за область функции view структуру.

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

По поводу перегрузок - да, не будет (хотя для square_matrix будет если добавить там SFINAE условие - оно же не конструируется по двум параметрам) работать для параметров, которые фактически участвуют в разрешении перегрузки, что логично.

Конструкция типа выше может использоваться для параметров, которые не участвуют в разрешении перегрузки, скажем вы сможете не указывать явно шаблонный параметр boost::lexical_cast в случае vector::push_back