Шаблон вычитания для функции на основе ее типа возврата? - PullRequest
27 голосов
/ 10 апреля 2010

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

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>?

Ответы [ 6 ]

30 голосов
/ 10 апреля 2010

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

// 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. C ++ 11 позволит вам пропустить одно из объявлений типов:

auto p = GC::Allocate<A>();   // p is of type GCPtr<A>
26 голосов
/ 10 апреля 2010

Единственное, о чем я могу думать: сделать 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();
}
8 голосов
/ 10 апреля 2010

Вы можете пойти противоположным путем.

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

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

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

4 голосов
/ 08 ноября 2013

(Этот ответ такой же, как у @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 в вашей программе.

1 голос
/ 10 апреля 2010

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

f();
0 голосов
/ 10 апреля 2010

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

#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>);
...