2015-04-14 2 views
2

Есть ли хороший способ получить идеальную пересылку для функций внутри шаблонного класса? В частности, в кодеИдеальная пересылка функций внутри шаблонного класса C++

#include <iostream> 

// Forward declare a Bar 
struct Bar; 

// Two different functions that vary based on the kind of argument 
void printme(Bar const & bar) { 
    std::cout << "printme: constant reference bar" << std::endl; 
} 
void printme(Bar && bar) { 
    std::cout << "printme: r-value reference bar" << std::endl; 
} 

void printme2(Bar const & bar) { 
    std::cout << "printme2: constant reference bar" << std::endl; 
} 
void printme2(Bar && bar) { 
    std::cout << "printme2: r-value reference bar" << std::endl; 
} 

// Some class with a bunch of functions and possible some data (though, not 
// in this one) 
template <typename T> 
struct Foo { 
    void baz(T && t) { 
     printme(std::forward <T> (t)); 
    } 
    void buz(T && t) { 
     printme2(std::forward <T> (t)); 
    } 
}; 

struct Bar {}; 

int main() { 
    Foo <Bar> foo;   
    foo.baz(Bar()); 

    // Causes an error 
    Bar bar; 
    //foo.buz(bar); 
} 

раскомментирован последний бит кода, мы получаем ошибку:

test03.cpp: In function 'int main()': 
    test03.cpp:51:16: error: cannot bind 'Bar' lvalue to 'Bar&&' 
     foo.buz(bar); 
        ^
    test03.cpp:30:10: note: initializing argument 1 of 'void Foo<T>::buz(T&&) [with T = Bar]' 
     void buz(T && t) { 
      ^
    Makefile:2: recipe for target 'all' failed 
    make: *** [all] Error 1 

Теперь мы можем решить эту проблему, перемещая аргумент шаблона внутри класса:

#include <iostream> 

// Forward declare a Bar 
struct Bar; 

// Two different functions that vary based on the kind of argument 
void printme(Bar const & bar) { 
    std::cout << "printme: constant reference bar" << std::endl; 
} 
void printme(Bar && bar) { 
    std::cout << "printme: r-value reference bar" << std::endl; 
} 

void printme2(Bar const & bar) { 
    std::cout << "printme2: constant reference bar" << std::endl; 
} 
void printme2(Bar && bar) { 
    std::cout << "printme2: r-value reference bar" << std::endl; 
} 

// Some class with a bunch of functions and possible some data (though, not 
// in this one) 
template <typename T> 
struct Foo { 
    void baz(T && t) { 
     printme(std::forward <T> (t)); 
    } 
    template <typename T_> 
    void buz(T_ && t) { 
     printme2(std::forward <T_> (t)); 
    } 
}; 

struct Bar { 
    Bar() {} 
}; 

int main() { 
    Foo <Bar> foo;   
    foo.baz(Bar()); 

    Bar bar; 
    foo.buz(bar); 
} 

Однако, похоже, это будет действительно многословный. Например, представьте, что у нас есть большое количество функций, которые все зависят от типа T и нуждаются в идеальной пересылке. Нам потребуются отдельные объявления шаблонов для каждого. Кроме того, класс Foo может содержать данные типа T и нам нужны функции, которые согласуются с этими данными. Конечно, typechecker поймает несоответствия, но эта система не так проста, как просто наличие одного аргумента шаблона, T.

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

+1

Мне нравится писать короткие обертки, как 'аннулируются Foo (T сопзИ & х)' и 'ничтожной Foo (T && х)' что оба вызова (с помощью 'std :: move' в последнем случае) - частный внутренний метод, который использует совершенную пересылку (' template void foo (U && x) ') и выполняет реальную работу. Таким образом, ваш открытый API является явным и ломает ошибки раньше, но почти нет дублирования в частных интранах. – Cameron

+0

Для функции, которая просто наблюдает за значением своих аргументов (и не изменяет их и не повышает эффективность, потенциально крадя их ресурсы), параметр 'const &' и безупречная пересылка по-прежнему являются жизнеспособным и часто лучшим решением. – dyp

ответ

3

Шаблоны: Очень разные.

В первом коде параметр шаблона равен Bar, поэтому я не уверен, что он делает, потому что вы НЕ должны использовать std::forward, за исключением ссылочных типов. I думаю это void buz(Bar&& t) {printme((Bar)t);}, и компилятор отказывается при передаче lvalue bar в main функции, ожидающей Bar&&.

Во втором кодоблока, параметр шаблона Bar& в связи с универсальной ссылкой, так что код void buz(Bar& t) {printme((Bar&)t);}, который связывается с к именующему bar в main просто отлично.

Если вы хотите совершенную пересылку, параметром шаблона должен быть присвоенный rvalue тип, который вы хотите передать, что означает, что вы почти всегда должны иметь функцию, которая должна быть шаблонизирована. Однако сделайте себе одолжение и назовите его другим. T и T_ будут различных типов.

template <typename U> 
void buz(U&& t) { 
    printme2(std::forward<U>(t)); 
} 

Если вы хотите SFINAE, вы можете добавить, что тоже:

template <typename U, 
    typename allowed=typename std::enable_if<std::is_constructible<Bar,U>::value,void*>::type 
    > 
void buz(U && t) { 
    printme2(std::forward <U> (t)); 
} 
+1

AFAIK, 'forward' также может быть полезен для параметров шаблона шаблонов классов.Рассмотрим 'template struct wrapper {T t; оператор T() && {return forward (t); }}; обертка > w {{1000, 42}}; вектор v; обертка &> x {v}; ' – dyp

+0

@Dyp: О, да, конечно. –

+0

Не нужно ли 'SFINAE' тестировать' U', чтобы убедиться, что это уместно? – Yakk

Смежные вопросы