Передача по значению всех типов, кроме string, в функцию шаблона


Я хочу определить шаблонную функцию, которая получает один аргумент, передаваемый по значению для всех типов, кроме std::stringconst char*).

template<typename T>
void foo( T value )
{
    // some code using value
}

Версия std::string должна вести себя точно так же, как версия template, но ее параметр передается через const&.

Каков наилучший подход, чтобы сделать то, что я хочу, не дублируя тело foo()?

Лучшее, что я смог придумать, это обернуть код с помощью value внутри другой функции, а затем вызвать ее внутри всех версий foo() (версия template и перегрузка std::string). Есть ли другой способ? Например, можно ли вызвать версию template из перегрузки std::string?

EDIT

То, что я хочу знать, - это хорошее эмпирическое правило для предотвращения дублирования кода между различными специализациями и перегрузками. Что такое хороший образец для подражания? Должен ли я определить функцию-оболочку для тела и затем вызвать ее из всех перегрузок/специализаций, или есть другой способ?
5 2

5 ответов:

Чтобы избежать дублирования кода, ответ по 101010 можно расширить, чтобы фактически вызвать шаблон из перегрузки:

#include <string>
#include <iostream>
#include <type_traits>
#include <boost/core/demangle.hpp>

template<typename T>
void foo( T value )
{
    std::cout << "inside template" << std::endl;
    std::cout << boost::core::demangle(typeid(value).name()) << std::endl;
}

void foo(const std::string &value)
{
    std::cout << "inside const string overload" << std::endl;
    foo<const std::string&>(value);
}
int main()
{
    foo(10);
    foo(std::string("hello"));
    return 0;
}

Вывод

inside template
int
inside const string overload
inside template
std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >

живой пример

Простое решение: обеспечить перегрузку для std::string:

void foo( std::string const &value ) {
    // some code using value
}

Я думаю, что вы ищете сигнатуру rvalue в C++ 11.

Это так просто, как:

#include <iostream>
#include <string>

template<typename T>
void foo(T&& value)
{
    std::cout << "was passed by refernece:" << std::is_lvalue_reference<T&&>::value << std::endl;  
    std::cout << value << std::endl;  
}

int main()
{
    std::string text = "hello";
    foo(text);
    foo(1);
}

Вы можете передать параметр либо по ссылке, либо по значению, и правила rvalue будут использовать соответствующий тип.

Можно определить класс типа-признака, который преобразует std::string в std::string& и сохранит тип для всех других типов:

template<class T>
struct helper {
    typedef T type;
};

template<>
struct helper<std::string> {
    typedef std::string& type; // or const std::string& type if you want
};

template<typename T>
void foo( typename helper<T>::type value, T value2 )
{
    value = value2;
}

int main()
{
    int a = 10;
    foo(a, 42);
    std::cout << a << std::endl; // prints 10

    std::string s = "abc";
    foo(s, std::string("def"));
    std::cout << s << std::endl; // prints def
}

Полный пример: http://coliru.stacked-crooked.com/a/96cf78e6c4846172

UPD: как отмечает @PiotrSkotnicki, наличие только одного параметра приводит к ошибке вывода типа. Тем не менее, я сохраню ответ, поскольку он может быть полезен в случае, если у вас действительно есть несколько параметров типа T или если вы согласны с указанием явного параметра шаблона для foo.

UPD2: чтобы решить проблему вычитания типа, вы можете добавить другую оболочку:

template<typename T>
void foo_helper( typename helper<T>::type value )
{
    value = T();
}

template<typename T>
void foo(T& value)
{
    foo_helper<T>(value);
}

Это все еще может иметь некоторые проблемы,так что применимо ли это к вашей usecase, зависит от вас.

Использование std::enable_if + std::is_convertibale:

   template<typename T>
    typename std::enable_if<!std::is_convertible<T,std::string>::value>::type foo( T value )
    {
        // some code using value
    }