шаблонное метапрограммирование — Как реализовать класс MultiUnitValue с использованием C ++ TMP

Я написал этот класс на VC ++, который явно не соответствует стандарту (TBH кажется отсталым, потому что он не разрешен стандартом)
так

1.) Я хотел бы знать, как преобразовать его в стандарт C ++ 11

код:

#include <functional>
template <typename T, typename U, typename T_to_U >
class MultiUnitValue  //Multi for now == 2 :)
{
const T t;
T_to_U conversion_function;
public:
MultiUnitValue()    : t(0)
{}
MultiUnitValue(T t_): t(t_)
{}
template<typename V>
V in() const
{
BOOST_STATIC_ASSERT(0);// "you are trying to call in with type(unit) not supported"}
template<>
T in<T>() const
{
return t;
}
template<>
U in<U>() const
{
return conversion_function(t);
}};

использование:

auto f = [](int i){return i*2.54;};

MultiUnitValue<int, float,decltype(f)> muv(10);
auto rv = muv.in<float>();

2.) Я поставил BOOST_STATIC_ASSERT, чтобы предотвратить неправильное использование,
но похоже, что MSVC не создает его, если он не используется (как я и ожидал), но при попытке перенести его на g ++ 4.7 он запускался даже тогда, когда шаблон не был создан?
Замена его static_assert не работает … (почему я не могу представить себе концепцию моего бедняка в g ++ :(: P)

Есть ли способ обойти это?

6

Решение

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


Мои решения:

Версия C ++ 11

Ваш пример с моей версией, я считаю, это то, что вы хотите:

int main(){
auto f1 = [](int i){return i*2.54;};
auto f2 = [](int i){ std::stringstream ss; ss << i; return ss.str(); };
MultiUnitValue<int, float, std::string>  vv(1, f1, f2);
std::cout << vv.in<int>() << "\n";
std::cout << vv.in<float>() << "\n";
std::cout << vv.in<std::string>() << "\n";
// std::cout << vv.in<long>() << "\n"; // error to compile
}

Прежде всего — вам нужны специальные базовые классы преобразования, для отдельного преобразования вы увидите в следующем фрагменте кода, что вызов преобразования через функцию базового класса вызывает «не указанное» преобразование, например, для long не будет компилироваться.

template <class T, class U>
class Conversion {
public:
Conversion(const std::function<U(const T&)>& f) : f(f) {}
U convert (const T& v) const { return f(v); }
private:
std::function<U(const T&)>  f;
};
template <class T>
class Conversion<T,T> {
public:
T convert (const T& v) const { return v; }
};

И ваш класс с использованием шаблонов variadic:

template <class T, class... V> // V... means all desired conversions
class MultiUnitValue : private Conversion<T,T>, private Conversion<T,V>... {
// allowed conversion:         ^^^^^^^^^^^^^^^^^        ^^^^^^^^^^^^^^^^^^
public:
MultiUnitValue(T v, const std::function<V(const T&)>&... f) : Conversion<T,V>(f)..., v(v) {}

template <class U>
U in() const
{
// this static assert is not needed - but just to show the message
static_assert(std::is_base_of<Conversion<T,U>, MultiUnitValue<T,V...>>::value,
"Not allowed conversion");

// static_assert is not needed
// since if you MultiUnitValue does not derive from Conversion<T,U>
// - then this call will not compile too
return this->Conversion<T,U>::convert(v);
}

private:
T v;
};

Пример LVS: http://liveworkspace.org/code/05b6ada146cc8f05d027a5536859a087

Версия без вариационных шаблонов:

Я также подготовил решение без шаблонов переменных, поскольку VC ++ до сих пор их не поддерживает.

Второе: ограничение конверсии и покрытия должно быть теперь в вашем типе T_to_U.

При таком подходе использование будет немного неудобным по сравнению с версией C ++ 11:

int main(){
auto f1 = [](int i){return i*2.54;};
auto f2 = [](int i){ std::stringstream ss; ss << i; return ss.str(); };
// next 2 lines differ from C++11 version
typedef ConvertFunctions2<int, float, std::string> CF_f1_f2;
MultiUnitValue<int, CF_f1_f2>  vv(1, CF_f1_f2(f1, f2));
std::cout << vv.in<int>() << "\n";
std::cout << vv.in<float>() << "\n";
std::cout << vv.in<std::string>() << "\n";
// std::cout << vv.in<long>() << "\n"; // error to compile
}

MultiUnitValue будет проще, чем в вашем примере, проще даже из моей версии C ++ 11, но class CF будет намного сложнее

template <class T, class CF>
class MultiUnitValue {
public:
MultiUnitValue(T v, const CF& cf) : v(v), cf(cf) {}

template <class U>
U in() const
{
return cf.Conversion<T,U>::convert(v);
}

private:
T v;
CF cf;
};

Простые «вспомогательные» классы преобразования будут такими же, как в версии C ++ 11:

template <class T, class U>
class Conversion {
...
};
template <class T>
class Conversion<T,T> {
...
};

И альтернатива шаблону variadic в VC ++ (и в старые времена C ++ 03):

template <class T>
class ConvertFunctions0 : public Conversion<T,T> {};

template <class T, class V1>
class ConvertFunctions1 : public Conversion<T,T>, public Conversion<T,V1> {
public:
ConvertFunctions1(std::function<V1(const T&)> f1) : Conversion<T,V1>(f1) {}
};

template <class T, class V1, class V2>
class ConvertFunctions2 : public Conversion<T,T>, public Conversion<T,V1>, public Conversion<T,V2> {
public:
ConvertFunctions2(std::function<V1(const T&)> f1, std::function<V2(const T&)> f2)
: Conversion<T,V1>(f1), Conversion<T,V2>(f2)
{}
};

Как видите — добавление ConvertFunctions3, ConvertFunctions4 не такая большая беда …

Полный пример на ideone

5

Другие решения

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

private:
T in(T *) const { return t; }
U in(U *) const { return conversion_function(t); }
template<typename V> V in(V *) const {
static_assert((V *)0, "you are trying to call in with type(unit) not supported");
}
public:
template<typename V> V in() const { return in((V *)0); }

Это также демонстрирует, как решить проблему с static_assert всегда стреляет; Вы должны сделать его выражение утверждения зависимым от параметра шаблона.

Следующая проблема заключается в том, что ваш шаблон не будет создан с параметром шаблона типа лямбда, потому что лямбды (даже лямбды без захвата) не могут быть созданы по умолчанию. Вы можете исправить это, вернувшись к традиционному функтору (struct с operator()) или преобразуйте лямбда-тип в конструируемый по умолчанию тип, который вызывает лямбду.

Одна из стратегий заключается в том, чтобы при вызове перенаправлять указатель void, поскольку лямбда без захвата не должна заботиться о том, что их this указатель:

template<typename L> struct default_constructible_lambda {
template<typename... Args> auto operator()(Args &&...args) const
-> decltype(std::declval<L>()(std::forward<Args>(args)...)) {
return (*(L *)(0))(std::forward<Args>(args)...); }
};
MultiUnitValue<int, float, default_constructible_lambda<decltype(f)>> muv(10);

Это все еще неопределенное поведение; это соответствует стандарту для указателя лямбда-функции, который будет храниться в типе замыкания, и в этом случае это приведет к вызову через указатель void.

1

По вопросам рекламы [email protected]