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:
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.
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...
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
How Does the Ampersand(&) Sign Work in C++
Nice Way to Append a Vector to Itself
C++11 Allows In-Class Initialization of Non-Static and Non-Const Members. What Changed
Append an Int to a Std::String
Difference Between a Pointer and Reference Parameter
Pointers, Smart Pointers or Shared Pointers
Createprocess from Memory Buffer
How to Get Error Message When Ifstream Open Fails
Is the Size of Std::Array Defined by Standard
How to Convert String to Char Array in C++
Can Placement New For Arrays Be Used in a Portable Way
Are Variable Length Arrays There in C++
Do I Have to Acquire Lock Before Calling Condition_Variable.Notify_One()
How to Check If Given C++ String or Char* Contains Only Digits