How to check if a specialized template function exists

I control the conversion of units. Tell us that I have reached the state in which I am achieving this.

The heart of my conversion between different units rests on the following common template function:

template <class SrcUnit, class TgtUnit> extern double convert(double val); 

The purpose of this function is to convert a physical quantity, expressed in units of type SrcUnit , into another, expressed in units of type TgtUnit .

I have a class called Quantity<Unit> that manages values โ€‹โ€‹and their unities, and this class is trying to provide type safety and automatic conversion. For example, I export the following constructor:

  template <class SrcUnit> Quantity(const Quantity<SrcUnit> & q) : unit(UnitName::get_instance()) { check_physical_units(q); // verify that the physical magnitudes are the same value = convert<SrcUnit, UnitName>(q.value); // <<- here the conversion is done check_value(); // check if the value is between the allowed interval } 

I export other material where the conversion is done.

So, when someone wants to manage a new module, she points to the new Unit class. I achieve this by inserting into the macro all the necessary specification of the new class. Now the user must write the conversion functions. That is, write two specializations of the convert() template. For example, suppose you have a unit called "Kilometer and you wish to specify a new unit called Mile`. In this case, you do the following:

 Declare_Unit(Mile, "mi", "English unit of length", Distance, 0, numeric_limits<double>::max()); // macro instantiating a new Unit class template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; } template <> double convert<Mile, Kilometer>(double val) { return 1609.344*val; } 

Now, what happens if the user forgets to write a conversion function? Well, in this case, the linker will fail because it cannot find the specialization of convert() .

Now my question.

Although I think the linker error is acceptable, since the behavior for the message to the user is missing convert() , I would like to check the compilation time I have for the existence of the convert() specialization. So my question is: how can I achieve this? I think through static_assert put before each call to convert() , which checks if the specialization is known. But how to do that?

PS: It would also be very useful for me if someone could recommend me a good text about C ++ metaprogramming.

+5
source share
4 answers

You could do this by placing static_assert in the main function template with a little trick, which will ensure that your program will not be poorly formed, shamelessly stolen from here :

 template <typename...> struct always_false : std::false_type {}; template<typename T, typename U> double convert(double) { static_assert(always_false<T, U>::value, "No specialization exists!"); } template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; } template <> double convert<Mile, Kilometer>(double val) { return 1609.344*val; } 

Live demo

+4
source

You should not promise the compiler that for every possible SrcUnit/TgtUnit there is a conversion:

 template <class SrcUnit, class TgtUnit> double convert(double val); 

Instead, you should do otherwise, remove the shared converter:

 template <class SrcUnit, class TgtUnit> double convert(double val) = delete; 

And for every conversion you provide, you have to send an ad to the header file / files:

 template <> double convert<Mille,Km>(double val); template <> double convert<Milliseconds,Hours>(double val); 

So, in this way it will be known that the compiler will be provided, which is not


In addition, your original decision may also be in danger of violating the rules of determination. Linker could not detect if you mistakenly provide two different implementations of the same conversion.

+4
source

I would approach this with tags.

 template<class T>struct tag_t{constexpr tag_t(){}; using type=T;}; template<class T>constexpr tag_t<T> tag{}; 

this allows you to pass types as parameters to a function.

Now, instead of using specialization, etc., we simply call the function.

We will use a better name than convert , for example unit_convert .

 namespace my_ns { Declare_Unit(Mile, "mi", "English unit of length", Distance, 0, numeric_limits<double>::max()); // macro instantiating a new Unit inline double unit_convert( tag_t<Meter>, tag_t<Mile>, double val ) { return val/1609.344 } inline double unit_convert( tag_t<Mile>, tag_t<Meter>, double val ) { return val*1609.344 } } 

Next, we write a line that says that if unit_convert( tag<A>, tag<B>, double ) exists:

 namespace details { template<template<class...>class Z, class=void, class...Ts> struct can_apply:std::false_type{}; template<template<class...>class Z, class...Ts> struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{}; } template<template<class...>class Z, class...Ts> using can_apply=details::can_apply<Z,void,Ts...>; template<class...Args> using convert_unit_result = decltype( convert_unit( std::declval<Args>()... ) ); template<class SourceUnit, class DestUnit> using can_convert_units = can_apply< convert_unit_result, tag_t<SourceUnit>, tag_t<DestUnit>, double >; 

and now can_convert_units<Mile,Meter> is std :: true_type, and can_conver_units<Chicken, Egg> is std :: false_type (assuming chicken and egg are types that are not convertible units, of course).

As a bonus, this method allows you to place your units in separate namespaces and define their conversion functions there. If both Mile and Meter try to determine the conversion between them, you will get errors when trying to convert (ambiguous). The conversion function can be in any namespace, and not both.

+4
source

Perhaps this may help.

 template<typename Src, typename Tgt, typename Exists = void> double convert (double val) { static_assert(not std::is_void<Exists>::value, " err msg"); } template <> double convert<Kilometer, Mile>(double val) { return val/1609.344; } 
0
source

All Articles