Nano Hash - криптовалюты, майнинг, программирование

Вывод шаблона для функции на основе ее возвращаемого типа?

Я хотел бы иметь возможность использовать вывод шаблона для достижения следующего:

GCPtr<A> ptr1 = GC::Allocate();
GCPtr<B> ptr2 = GC::Allocate();

вместо (что у меня сейчас есть):

GCPtr<A> ptr1 = GC::Allocate<A>();
GCPtr<B> ptr2 = GC::Allocate<B>();

Моя текущая функция Allocate выглядит так:

class GC
{
public:
    template <typename T>
    static GCPtr<T> Allocate();
};

Можно ли убрать лишние <A> и <B>?


  • между прочим, у меня было что-то подобное, но с типом возвращаемого значения, основанным на типах аргументов конструктора. я сделал шаблонную вспомогательную функцию make_complex_template_obj(the, args), поэтому я могу использовать auto при инициализации переменных из этой функции. предположительно по той же причине, что и в принятом ответе, этому шаблону не может быть присвоен тип возврата auto. к счастью, я смог избежать дублирования имени типа в return, так как к тому времени он знал, какой тип подходит, и соответствующим образом преобразовывал голый список инициализаторов. настоящее приключение! 04.01.2016

Ответы:


1

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

// helper
template <typename T>
void Allocate( GCPtr<T>& p ) {
   p = GC::Allocate<T>();
}

int main()
{
   GCPtr<A> p = 0;
   Allocate(p);
}

Является ли этот синтаксис на самом деле лучше или хуже исходного GCPtr<A> p = GC::Allocate<A>() — это другой вопрос.

P.S. С++ 11 позволит вам пропустить одно из объявлений типа:

auto p = GC::Allocate<A>();   // p is of type GCPtr<A>
10.04.2010

2

Единственное, что я могу придумать: сделать Allocate не-шаблоном, который возвращает не-шаблонный прокси-объект, который имеет шаблонный оператор преобразования, который выполняет реальную работу:

template <class T>
struct GCPtr
{

};

class Allocator
{
public:
    template <class T>
    operator GCPtr<T>() { return GCPtr<T>(); }
};

class GC
{
public:
    static Allocator Allocate() { return Allocator(); }//could give a call-back pointer?
};

int main()
{
    GCPtr<int> p = GC::Allocate();
}
10.04.2010
  • Вроде перебор, но все же, я не знал этой закономерности. Ты научил меня кое-чему. Так что +1. 10.04.2010
  • Во всяком случае, на первый взгляд, я думаю, вы могли бы вообще избежать GC::Allocate() и написать: GCPtr<int> p = Allocator() ;, нет? 10.04.2010
  • Как говорится в комментарии, объект Allocator может хранить дополнительные данные, которые он получает через конструктор, поэтому GC::Allocate может решить, какие данные ему нужны для операции. - В конце концов, конструктор GCPtr<T> может сам выполнить работу (вызвать GC::Allocate<T>). 10.04.2010
  • Есть ли способ передать аргумент в Allocator? Что-то вроде godbolt.org/z/nScp8c 08.05.2020

  • 3

    Можно было пойти обратным путем.

    Если вы используете современный компилятор (MSVC 2010, который должен выйти через пару дней, или текущую версию GCC) и не возражаете полагаться на функции C++0x:

    auto ptr1 = GC::Allocate<A>();
    auto ptr2 = GC::Allocate<B>();
    

    сэкономит вам дополнительные <A> и <B>, просто не с правой стороны. :)

    10.04.2010

    4

    (Этот ответ такой же, как и у @UncleBens, но немного более общий, поскольку он идеально передает любые аргументы.)

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

    (Вот пример кода на ideone.)

    Во-первых, начнем с функции foo, тип возвращаемого значения которой мы хотим вывести:

    template<typename Ret>
    Ret foo(const char *,int);
    template<>
    std::string foo<std::string>(const char *s,int) { return s; }
    template<>
    int         foo<int        >(const char *,int i) { return i; }
    

    При запросе строки он вернет строку, указанную в первом аргументе. При запросе int он вернет второй аргумент.

    Мы можем определить функцию auto_foo, которую можно использовать следующим образом:

    int main() {
            std::string s = auto_foo("hi",5); std::cout << s << std::endl;
            int         i = auto_foo("hi",5); std::cout << i << std::endl;
    }
    

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

    #include<tuple>
    
    template<size_t num_args, typename ...T>
    class Foo;
    template<typename ...T>
    class Foo<2,T...> : public std::tuple<T&&...>
    {
    public: 
            Foo(T&&... args) :
                    std::tuple<T&&...>(std::forward<T>(args)...)
            {}
            template< typename Return >
            operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this)); }
    };
    template<typename ...T>
    class Foo<3,T...> : std::tuple<T&&...>
    {
    public: 
            Foo(T&&... args) :
                    std::tuple<T&&...>(std::forward<T>(args)...)
            {}
            template< typename Return >
            operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this), std::get<2>(*this)); }
    };
    
    template<typename ...T>
    auto
    auto_foo(T&&... args)
            // -> Foo<T&&...> // old, incorrect, code
            -> Foo< sizeof...(T), T&&...> // to count the arguments
    {
            return              {std::forward<T>(args)...};
    }
    

    Кроме того, приведенное выше работает для функций с двумя или тремя аргументами, нетрудно понять, как это расширить.

    Это очень много кода для написания! Для каждой функции, к которой вы хотите применить это, вы можете написать макрос, который сделает это за вас. Что-то вроде этого в верхней части вашего файла:

    REGISTER_FUNCTION_FOR_DEDUCED_RETURN_TYPE(foo); // declares
                            // necessary structure and auto_???
    

    и тогда вы можете использовать auto_foo в своей программе.

    08.11.2013
  • Я нахожу это довольно интересным, но я считаю, что вам не хватает параметра специализации в auto_foo : auto auto_foo(T&&... args) -> Foo<sizeof...(T), T&&...>, потому что иначе он не выберет специализацию ИМХО. 19.05.2014
  • Ты прав. Я обновлю код здесь. Я проверил код на своем компьютере, но, очевидно, я не скопировал его точно. Спасибо! 19.05.2014
  • В любом случае это хороший способ реализовать это. Спасибо за пример. 20.05.2014
  • Интересное решение, есть ли причина, по которой вы выбрали std::tuple_size вместо прямого использования sizeof...(T)? 23.05.2014
  • Нет причин, @daminetreg. Я изменил его сейчас. Я просто скопировал и вставил его из моего рабочего примера, и я не знаю, как я вообще написал это таким образом! (Обновление: возможно, сначала я попробовал sizeof(T)..., думая, что ... всегда идет в конце выражения, в котором должно происходить раскрытие. Но это так не работает, поэтому, возможно, поэтому я выбрал tuple_size вместо этого) 27.05.2014
  • Нужно ли, чтобы функция foo имела параметры (const char *,int)? Если мне нужна та же функция для функции template <typename _Ty, int N> SquareMatrix<_Ty, N> MakeIdentityMatrix(), которая вообще не требует ввода? 01.01.2020

  • 5

    Точно так же вы не можете перегружать функции по возвращаемому типу, вы не можете делать вывод по шаблону. И по той же причине - если f() является шаблоном/перегрузкой, которая что-то возвращает, какой тип здесь использовать:

    f();
    
    10.04.2010
  • Ну я уже думал об этом. Мой класс сборщика мусора использует подсчет ссылок, и вызов GC::Allocate() по своей сути будет иметь 0 ссылок, которые в любом случае просто будут очищены. Это, конечно, если код скомпилирован/ 10.04.2010
  • Ошибка компилятора, если не появляется в приведении ((int)f();) ...? 10.04.2010
  • @UncleBens: хорошая идея! Однако компилятор C++ в настоящее время не работает таким образом. 10.04.2010
  • @Neil, я пытался сказать, что уже думал о том, что происходит, когда f() вызывается сама по себе (ошибка компиляции). Теперь замените f() на GC::Allocate() и представьте, что он скомпилировался. Мой сборщик мусора использует подсчет ссылок, и, поскольку возвращаемое значение не сохраняется в GCPtr, счетчик ссылок равен 0, и сборщик мусора просто очистит его мгновенно. Это все гипотетически, так как код на самом деле не компилируется. 10.04.2010
  • @Neil: Я имею в виду, что именно так перегрузка и вывод типа на основе возвращаемого типа могли бы гипотетически работать, если бы они существовали. 10.04.2010
  • @UncleBens Ну ладно, гипотетически ... Я не думаю, что это было бы достаточной причиной для повторного введения приведения в стиле C. И это заставило бы перегруженные функции работать иначе, чем неперегруженные, WRT тип возвращаемого значения. 10.04.2010
  • @Neil: Вы тоже можете использовать static_cast. Я не думаю, что этот механизм будет слишком отличаться от устранения неоднозначности между перегрузками путем приведения аргументов (или выбора вызываемого оператора преобразования). Неудобно использовать его везде, но приятно иметь его, если он вам нужен. 10.04.2010
  • @UncleBens Но что, если вы действительно хотите привести возвращаемое значение? 10.04.2010
  • @Нил: Хороший вопрос. int foo(); long foo(); static_cast<double>(foo()); -› неоднозначные перегрузки. Обходной путь: long l = foo(); static_cast<double>(l); или static_cast<double>(static_cast<long>(foo()); 10.04.2010
  • @Neil: Тогда вам придется сделать два вложенных приведения. Нехорошо, но есть случаи, когда мы должны сделать это сейчас. Обратите внимание, что в Аде десятилетиями применялась перегрузка, основанная на типе возвращаемого значения. (Не то чтобы мне отчаянно не хватало этого в C++, просто это, в принципе, возможная функция.) 10.04.2010

  • 6

    Вы можете попробовать использовать макрос для этого. Кроме этого, я не понимаю, как это должно работать только с одним оператором.

    #define ALLOC(ptrname,type) GCPtr<type> ptrname = GC::Allocate<type>()
    
    ALLOC(ptr1,A);
    

    Пункты Йоханнеса действительны. Проблема >> легко решается. Но я думаю, что запятые как часть типа требуют расширения varargs препроцессора C99:

    #define ALLOC(ptrname,...) GCPtr< __VA_ARGS__ > ptrname = GC::Allocate< __VA_ARGS__ >()
    
    ALLOC(ptr1,SomeTemplate<int,short>);
    
    10.04.2010
  • Обратите внимание, что этот макрос не работает, если вы выполняете ALLOC(ptr1, A<a, b>); (есть две проблемы: нет пробела после type (он же >>) и запятая образует два аргумента макроса из A<a, b>). 10.04.2010
  • И что бы это купило вас? Вам все равно придется указывать тип, и это менее безопасно, чем решение Дэвида со встроенным шаблоном функции. -1 от меня. 10.04.2010
  • Вы можете решить обе проблемы, сказав ALLOC(ptr1, (A<a, b>)); и переписав макрос, чтобы передать тип функции в template<typename T> struct ty; template<typename Ty> struct ty<void(Ty)> { typedef Ty type; }; и вместо этого сказать GCPtr<ty<void type>::type> ptrname (и то же самое с typename для использования в шаблонах. С++ 0x и некоторые текущие компиляторы С++ 03 позволяют typename также однако вне шаблонов). 10.04.2010
  • @sbi: Конечно, я бы не стал использовать такой макрос в своем коде. Что это было единственное, что пришло мне в голову. И, конечно, вы должны назвать тип хотя бы один раз, даже с решением Давида. 10.04.2010
  • @ltb: это очень умно, чтобы обойти макросы C99 varargs. Но у него есть проблема, заключающаяся в том, что вам нужны две версии, если тип зависит от аргументов шаблона. 10.04.2010
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..


    © 2024 nano-hash.ru, Nano Hash - криптовалюты, майнинг, программирование