Metaprograming: Failure of Function Definition Defines a Separate Function

Metaprograming: Failure of Function Definition Defines a Separate Function

Freshly voted into the library fundamentals TS at last week's committee meeting:

template<class T>
using to_string_t = decltype(std::to_string(std::declval<T>()));

template<class T>
using has_to_string = std::experimental::is_detected<to_string_t, T>;

Then tag dispatch and/or SFINAE on has_to_string to your heart's content.

You can consult the current working draft of the TS on how is_detected and friends can be implemented. It's rather similar to can_apply in @Yakk's answer.

stuck with C++ metaprogramming

When you call your call function, you pass the function f as an argument. However c++ implicitly converts the argument to a pointer-to-function. Therefore, when the type-alias is being constructed, the template parameter is actually int(*)(int,float), not int(int,float). Since this does not meet the requirements of the partially specialised template, the compiler attempts to construct the type-alias from the non-specialised template for makeTupleOfParams. However the non-sepcialised template does not contain the type-alias "type", which results in a compile-error.

To solve, modify the partially-specialised template to:

template<typename Return, typename... Params>
struct makeTupleOfParams<Return(*)(Params...)> //pointer-to-function specialisation
{
using type = std::tuple<Params...>;
};

Another solution is to use type_traits to remove the pointer trait within the call function, like so:

#include<type_traits>

template <typename Function, typename... Params>
auto call(Function f, Params... p)
{
// getting size of Params and argument types of Function
constexpr size_t paramsCount = sizeof...(Params);
// modify function type to remove pointer trait
tupleOfParamTypes_t<std::remove_pointer_t<Function>> params;

return dispatchParams(f, params, std::make_index_sequence<paramsCount>());
}

In this case, the template parameter passed to tupleOfParamTypes_t will be int(int,float).

Use of void template argument in early detection idiom implementation

Judging on how the authors wrote their final implementation of is_detected, they intended that Op be a variadic template, which allows one to express many more concepts:

(Also pulled from n4502)

// primary template handles all types not supporting the archetypal Op:
template< class Default
, class // always void; supplied externally
, template<class...> class Op
, class... Args
>
struct
detector
{
using value_t = false_type;
using type = Default;
};
// the specialization recognizes and handles only types supporting Op:
template< class Default
, template<class...> class Op
, class... Args
>
struct
detector<Default, void_t<Op<Args...>>, Op, Args...>
{
using value_t = true_type;
using type = Op<Args...>;
};
//...
template< template<class...> class Op, class... Args >
using
is_detected = typename detector<void, void, Op, Args...>::value_t;

When you get into a scenario like this, a void becomes necessary so that template specialization will match the true_type version when Op<Args...> is a valid expression.

Here's my tweak on the original detect to be variadic:

// primary template handles all types not supporting the operation:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect : std::false_type { };
// specialization recognizes/validates only types supporting the archetype:
template< class T, template<class...> class Trait, class... TraitArgs >
struct
detect< T, Trait, std::void_t<Trait<T, TraitArgs...>>, TraitArgs... > : std::true_type { };

template<class T, template<class...> class Trait, class... TraitArgs>
using is_detected_t = typename detect<T, Trait, void, TraitArgs...>::type;

template<class T, template<class...> class Trait, class... TraitArgs>
constexpr bool is_detected_v = detect<T, Trait, void, TraitArgs...>::value;

Note that I renamed Op to Trait, Args to TraitArgs, and used std::void_t which made it into C++17.

Now let's define a trait to test for a function named Foo that can may or may not accept certain parameter types:

template<class T, class... Args>
using HasFoo_t = decltype( std::declval<T>().Foo(std::declval<Args>()...));

Now we can get a type (true_type or false_type) given some T and our trait:

template< class T, class... Args>
using has_foo_t = is_detected_t<T, HasFoo_t, Args...>;

And finally, we can also "just check" to see if the trait is valid for some provided T and Args:

template<class T, class... Args>
constexpr bool has_foo_v = is_detected_v<T, HasFoo_t, Args...>;

Here's a struct to start testing:

struct A
{
void Foo(int)
{
std::cout << "A::Foo(int)\n";
}
};

And finally the test(s):

std::cout << std::boolalpha << has_foo_v<A, int> << std::endl; //true
std::cout << std::boolalpha << has_foo_v<A> << std::endl; // false

If I remove the void from my is_detected_t and is_detected_v implementations, then the primary specialization is chosen, and I get false (Example).

This is because the void is there so as to match std::void_t<Trait<T, TraitArgs...>> which if you recall will have a type of void if the template argument is well-formed. If the template argument is not well-formed, then std::void_t<Trait<T, TraitArgs...>> is not a good match and it will revert to the default specialization (false_type).

When we remove void from our call (and simply leave TraitArgs... in its place) then we cannot match the std::void_t<Trait<T, TraitArgs...>> argument in the true_type specialization.

Also note that if std::void_t<Trait<T, TraitArgs...>> is well-formed, it simply provides a void type to the class... TraitArgs argument in the primary template, so we don't need to define an extra template parameter to receive void.

In conclusion, the authors wanted to remove the void that would end up in client code, hence their slightly more complicated implementation later in the paper.

Thanks to @Rerito for pointing out this answer where Yakk also puts in a little extra work to avoid the pesky void in client code.

Is partial specialization in a cpp file not well-formed

Your code is ill-formed, no diagnostic required, according to [temp.class.spec]:

A partial specialization shall be declared before the first use of a class
template specialization that would make use of the partial specialization as the result of an implicit or
explicit instantiation in every translation unit in which such a use occurs; no diagnostic is required.

That said, for such a case, you don't need a partial specialization at all, and can simply forward along your members:

// from the primary
void print() {
print("T1", d_t1);
print("T2", d_t2);
}

Where:

// has print()
template <typename T,
typename = std::enable_if_t<has_print<T>::value>>
void print(const char* name, const T& val) {
std::cout << name << " is: ";
val.print();
std::cout << std::endl;
}

// has noPrint()
template <typename T,
typename = std::enable_if_t<has_noprint<T>::value>>
void print(const char* /* unused */, const T& val) {
std::cout << "NoPrint: ";
val.noPrint();
std::cout << std::endl;
}

I'll leave the implementation of those type traits as an exercise to the reader. For a guide to several different ways of how to write such a thing, see this question, with solutions involving std::experimental::is_detected, void_t, overload resolution with trailing-return-type, can_apply, and REQUIRES.

Metaprograming: Failure of Function Definition Defines a Separate Function

Freshly voted into the library fundamentals TS at last week's committee meeting:

template<class T>
using to_string_t = decltype(std::to_string(std::declval<T>()));

template<class T>
using has_to_string = std::experimental::is_detected<to_string_t, T>;

Then tag dispatch and/or SFINAE on has_to_string to your heart's content.

You can consult the current working draft of the TS on how is_detected and friends can be implemented. It's rather similar to can_apply in @Yakk's answer.

Can I make separate definitions of function template members of a class template?

I gave it a thought, read language specs etc. and the following things come to my mind:

  1. Class template has to be specialized in order to specialize member function template. Period. This cannot be overcome with concepts, or at least I haven't found a way. I guess you don't want to replicate the code for each case of TS. Maybe it can be done automagically with some Alexandrescu-style metaprogramming techniques, but I can't think of anything right off the bat.

  2. Overloads instead of templates are a good alternative but I'm guessing you'd like to be able to add them out-of-line, instead of inside the class...

  3. Then I recalled David Wheeler: “All problems in computer science can be solved by another level of indirection." So let's add one:

namespace detail
{
template<typename TAG> auto getValue(TAG);

template<>
auto getValue<string_tag>(string_tag)
{
return "hi";
}


template<>
auto getValue<int_tag>(int_tag)
{
return 42;
}

template<>
auto getValue<double_tag>(double_tag)
{
return 1324.2;
}

}

template<bool TS>
template<typename TAG>
auto Wibble<TS>::getValue(TAG t)
{
return detail::getValue(t);
}

https://godbolt.org/z/GsPK4MP8M
Hope it helps.



Related Topics



Leave a reply



Submit