I wrote this class in VC++, that is obviously non standard compliant (TBH seems weird that it isn't allowed by the standard)
Code:
#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);
}
};
Usage:
auto f = [](int i){return i*2.54;};
MultiUnitValue<int, float,decltype(f)> muv(10);
auto rv = muv.in<float>();
Any way to work around this?
Your solution was not "standard compliant", because member functions cannot be specialized in class template. This is because of general rule that functions cannot be partially specialized - so even "full" specialization of member function template is actually partial specialization because of not fully specialized class.
My solutions:
Your example with my version, I believe this is what you want:
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
}
First of all - you need the special conversion base classes, for a single conversion, you will see in next code fragment that calling conversion via base class function causes that "non specified" conversion, like this for long
will not compile.
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; }
};
And your class with using variadic templates:
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 example: http://liveworkspace.org/code/05b6ada146cc8f05d027a5536859a087
I also prepared solution without variadic templates, since VC++ still does not support them.
Second: the conversion and coversion limitation should be now in your T_to_U type.
With this approach usage will slightly inconvenient in the comparison to C++11 version:
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
}
The MultiUnitValue
will be simpler than in your example, simpler even from my C++11 version, but the class CF
will be much more complicated:
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;
};
The simple "helper" conversion classes will be identical as in C++11 version:
template <class T, class U>
class Conversion {
...
};
template <class T>
class Conversion<T,T> {
...
};
And the variadic template alternative in VC++ (and in old days of 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)
{}
};
As you can see - adding ConvertFunctions3
, ConvertFunctions4
is not so big trouble...
Full example at ideone
The first issue is that you can't specialize member function templates within a class, and you can't specialize them outside a (template) class because then they'd be partial function specializations. The easiest way to work around this is to use private member function overloads:
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); }
This also demonstrates how to fix the issue with the static_assert
always firing; you need to make its assertion expression dependent on a template parameter.
The next issue is that your template won't instantiate with a lambda type template parameter, because lambdas (even captureless lambdas) are not default-constructible. You can fix this by either going back to a traditional functor (struct
with operator()
) or convert the lambda type to a default-constructible type that calls the lambda.
One strategy is to indirect a void pointer when calling, since captureless lambdas shouldn't care what their this
pointer is:
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);
This is still undefined behaviour, though; it's consistent with the standard for the lambda's function pointer to be stored within the closure type, in which case this would result in calling through a void pointer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With