I wrote a simple SFINAE-based trait to detect whether a class has a method with a specific signature (the back story is that I am trying to detect whether a container T has a method T::find(const Arg&) - and if yes, then store a pointer to this method).
After some testing I discovered a case where this trait would give a false positive result - if T defines the method as a template but definition compiles not for all possible Ts. This is best demonstrated with an example:
#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
using namespace std;
// HasFooWithArgument is a type trait that returns whether T::foo(const Arg&) exists.
template <typename T, typename Arg, typename = void>
inline constexpr bool HasFooWithArgument = false;
template <typename T, typename Arg>
inline constexpr bool HasFooWithArgument<T, Arg, std::void_t<decltype(std::declval<const T&>().foo(std::declval<const Arg&>()))>> = true;
// Below are the test cases. struct E is of the most interest.
struct A {};
struct B {
void foo(int x) const {}
};
struct C {
void foo(int x) const {}
void foo(const std::string& x) const {}
};
struct D {
template <typename T>
void foo(const T& x) const {}
};
struct E {
// Any T can be substituted (hence SFINAE "detects" the method)
// However, the method compiles only with T=int!
// So effectively, there is only E::foo(const int&) and no other foo methods...
template <typename T>
void foo(const T& x) const {
static_assert(std::is_same_v<T, int>, "Only ints are supported");
}
};
int main()
{
cout << std::boolalpha;
cout<<"A::foo(int): " << HasFooWithArgument<A, int> << "\n";
cout<<"B::foo(int): " << HasFooWithArgument<B, int> << "\n";
cout<<"C::foo(int): " << HasFooWithArgument<C, int> << "\n";
cout<<"C::foo(string): " << HasFooWithArgument<C, std::string> << "\n";
cout<<"C::foo(std::vector<int>): " << HasFooWithArgument<C, std::vector<int>> << "\n";
cout<<"D::foo(string): " << HasFooWithArgument<D, std::string> << "\n";
cout<<"E::foo(int): " << HasFooWithArgument<E, int> << "\n";
cout<<"E::foo(string): " << HasFooWithArgument<E, std::string> << "\n";
E e;
e.foo(1); // compiles
// e.foo(std::string()); // does not compile
return 0;
}
The above prints:
A::foo(int): false
B::foo(int): true
C::foo(int): true
C::foo(string): true
C::foo(std::vector<int>): false
D::foo(string): true
E::foo(int): true
E::foo(string): true
Unfortunately, this last case is my main target: as I mentioned above, I am doing this to detect container find methods and they are usually defined as templates to support heterogeneous lookup.
In other words, std::set<std::string, std::less<>> is where my detection gives false positives as it returns true for any argument type.
Thanks in advance!
The issue you're describing is not due to the fact that SFINAE doesn't work properly with function templates (it does). It has to do with the fact that SFINAE can't detect whether a function body is well-formed.
Your approach should work just fine when it comes to detecting that std::set<std::string> does not have a find member that takes a std::string_view argument. This is because the templated find member does not participate in overload resolution unless the set has a transparent comparator. Since std::set<std::string> doesn't have a transparent comparator, its find function will only accept types that are implicitly convertible to std::string; any attempt to pass any other type will result in an overload resolution failure, which will be detected by SFINAE just like any other case of an argument type mismatch.
Where your approach won't work is with something like std::set<std::string, std::less<>> and trying to call find with an argument type of say, int. In this case the detection will succeed because the find template is unconstrained, but then compilation will fail later on if you try to actually call find with an int argument, because somewhere in the body, it will try to call std::less<> with types that can't be compared with each other (std::string and int).
Colloquially, we say that a function or a class that makes it possible to detect using SFINAE whether it, itself, would be well-formed, is "SFINAE-friendly". In that sense, std::set::find fails to be SFINAE-friendly. To make it SFINAE-friendly, it would have to guarantee that an overload resolution failure would occur if you tried to call it with a type that isn't comparable using the specified transparent comparator.
When something is not SFINAE-friendly, the only workaround is to special-case it: in other words, you have to put in your code a special case that makes detection fail for std::set::find in this case, by checking explicitly whether the comparator accepts the argument type. (std::less<> is SFINAE-friendly: it does the overload resolution in the (explicitly specified) return type.)
Related
How does this code not compile? Why can bs[1] not be deduced to bool?
Is there a generic way to solve this problem?
#include <iostream>
#include <string>
#include <bitset>
using namespace std;
template<typename T> struct StringConverter{};
template<> struct StringConverter<bool>{
std::string operator()(const bool &i){ return i?"true":"false"; }
};
template<typename T> std::string to_string(const T &val){
return StringConverter<T>()(val);
}
int main(){
// this does not compile
std::bitset<10> bs;
std::cout << to_string(bs[0]) << std::endl;
// this does
const std::bitset<10> bs_const;
std::cout << to_string(bs_const[0]) << std::endl;
}
Compiler Error:
main.cpp:12:12: error: type 'StringConverter<std::bitset<10>::reference>' does not provide a call operator
return StringConverter<T>()(val);
^~~~~~~~~~~~~~~~~~~~
main.cpp:18:18: note: in instantiation of function template specialization 'to_string<std::bitset<10>::reference>' requested here
std::cout << to_string(bs[0]) << std::endl;
^
1 error generated.
the non-const bitset::operator[] returns a proxy object rather than a bool (this has to be done because that proxy can be used to change the bit value). const bitset::operator[] however just returns a bool (not a reference, just a plain bool) so it matches for the StringConverter[
If you check the declaration of operator[], you'll notice it has two overloads - the const one, which returns bool and is used in your second example, and the non-const, which returns the object of type std::bitset::reference.
The latter is used for bit field modification, and it absolutely cannot be a bool& since it has to address a specific bit. The problem you ran into is quite common for these proxy return types (this is where I should mention vector<bool>).
As a possible solution you can use the fact that std::bitset::reference is convertible to bool (and is not convertible to any other conceivable type that you might use for your StringConverter specializations).
I am trying to create a templated function is compile-time enforced to use only specializations. I referenced Force a compile time error in a template specialization which suggests to use a static_assert on something inherited from std::false_type.
#include <iostream>
using namespace std;
template<typename T>
struct always_false : std::false_type {};
//Case: Default
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: bool
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
//Case: int
template<typename T, typename std::enable_if<!std::is_same<T,bool>::value && std::is_convertible<T,int>::value,int>::type=0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
int main() {
foo(true); //(Good) Works correctly
foo((int)5); //(Bad) Error: call of overload foo(int) is ambiguous
foo((unsigned int)10); //(Bad) Error: call of overload foo(unsigned int) is ambiguous
foo((void*)nullptr); //(Good) Error: static assertion failed
return 0;
}
When I pass in an int or unsigned int, the compiler complains that the call is ambiguous suggesting that it can use either Case: Default or Case: int.
This is confusing as the Case: Default has the always_false static_assert() and I would expect the compiler to disallow it.
My last example passing in a void* successfully triggers the static_assert() and causes a compile-time error.
I am new to programming using SFINAE template metaprogramming, so I suspect I am doing something wrong in the Case: int specialization
Two questions:
Why is foo(int) in this code ambiguous?
Is there a better way to use
templates to get this desired behavior (explicit bool specialization + implicit integers specialization)?
Why is foo(int) in this code ambiguous?
Because the version with static_assert() give error if selected but still exist; so the compiler doesn't know if choose the generic version or the integer enabled version.
Is there a better way to use templates to get this desired behavior (explicit bool specialization + implicit int specialization)?
A possible way is to avoid the generic version and SFINAE enable the version you need
The following is a full working example
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_same<T, bool>::value>::type foo(T val)
{ std::cout << "bool case " << val << std::endl; }
template <typename T>
typename std::enable_if< ! std::is_same<T, bool>::value
&& std::is_convertible<T, int>::value>::type foo(T val)
{ std::cout << "integer case " << (int)val << std::endl; }
int main()
{
foo(true); // bool case
foo(1); // integer case
foo(2U); // integer case
foo(3L); // integer case
foo(4UL); // integer case
foo(5LL); // integer case
foo(6ULL); // integer case
// foo((void*)nullptr); // compilation error
}
-- EDIT --
The OP
Sorry, I am still confused. Could you elaborate? I thought that due to SFINAE, that if an error occurred in substitution, it would use the other template.
Exactly.
The problem is when there isn't an error in substitution and the compiler have to choose between two different version of the same template.
I mean: in your example, when you call foo(5), there isn't error in substitution of
typename std::enable_if<!std::is_same<T,bool>::value
&& std::is_convertible<T,int>::value,int>::type=0>
So the compiler have to choose between the two template functions
template<typename T>
void foo(T val) {
static_assert(always_false<T>::value, "");
}
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
that differ only for a template value with a default value, so are (from the compiler point of view) indistinguishable.
And observe that
template<>
void foo<bool>(bool val) {
cout << "Is explicitly a bool! " << val << endl;
}
is a (full) template specialization but
//Case: int
template<typename T, int = 0>
void foo(T val) {
cout << "Can be implicitly converted to int! " << (int)val << endl;
}
isn't a template specialization (no partial template specialization of a function is admitted in C++11/14/17; you can partial specialize only structs/classes); is a generic template.
You could use SFINAE as suggested by #max66, but a simple way for your use case would be to have a bool overload and a templated version
void foo(bool);
template <class T>
void foo(T);
You can enforce that T is convertible to int (static_assert) but in most cases it is not necessary because the body of foo will probably be ill-formed in such case, thus leading to a compile-time error.
template <class T>
void foo(T) {
static_assert(std::is_convertible<T, int>::value, "");
}
With your examples:
foo(true); // foo(bool) is chosen because it is the best match
foo((int)5); // foo<int>(int) is chosen, the assertion passes
foo((unsigned int)10); // foo<unsigned int>(unsigned int) is chosen, assertion ok
foo((void*)nullptr); // foo<void*>(void*) is chosen, the assertion fails
I'm attempting to use SFINAE to detect if a type passed as a template argument T has an T::operator()(P const&) where P is also a template argument. I modeled my solution after this example of the Member Detector Idiom Unfortunately, I could not get this working for an operator(), even though I could get it to function for a normal method.
Here's some sample code that demonstrates the problem I'm facing:
#include <iostream>
#include <iomanip>
#include <utility>
#include <type_traits>
using namespace std;
struct has
{
void operator()(int const&);
};
struct hasNot1
{
void operator()(int);
};
struct hasNot2
{
void operator()();
};
struct hasNot3
{
void operator()(float);
};
struct hasNot4
{
};
template<typename T, typename EDT>
struct is_callable_oper
{
private:
typedef char(&yes)[1];
typedef char(&no)[2];
template <typename U, void (U::*)(EDT const &)> struct
Check;
template<typename>
static yes test(...);
template <typename U>
static no
test(Check<U, &U::operator()>*);
public:
static constexpr bool value = sizeof(test<T>(0))
== sizeof(yes);
};
int main() {
cout << boolalpha << is_callable_oper<has, int&>::value << " "
<< is_callable_oper<has, int>::value << " "
<< is_callable_oper<hasNot1, int&>::value << " "
<< is_callable_oper<hasNot2, int&>::value << " "
<< is_callable_oper<hasNot3, int&>::value << " "
<< is_callable_oper<hasNot4, int&>::value << endl;
return 0;
}
Running it on ideone (https://ideone.com/tE49xR) produces:
true false true true true true
I expected:
true false false false false false
Work Done:
Read this Stackoverflow question. I've also followed related links.
Looked up std::declval, decltype. I've also studied a bit on how the non-type template parameter results in an ambiguity. I've been using http://en.cppreference.com/ primarily.
Read some other related questions and links.
Note: I'm working on gcc 4.6.3 with C++ 0x.
End Goal: Detect all callables function pointers included with this signature.
Related Clarifications: I'm still confused about some concepts, and I would be grateful if you could answer these. Of course, if they belong in a separate question, please do let me know.
Can we trigger the ambiguity for SFINAE by using declval rather than a Check template for this case?
What is the function type for an overloaded operator? For example, would the overloaded operator in this case have the following type: void (operator())(EDT const&)?
As CV Qualifiers are discarded during this check, what is the best way to check for the const-ness of the argument I'm passing.\
I haven't been able to figure out a way to use Boost to do this. I'm also stuck using an older Boost version 1.43 (Will check and update the exact), I believe. If there is no reason to roll my own check, that may be for the best.
I'm still very much a beginner in this area, and I sincerely apologize if this is too basic. I would appreciate it if you can point me to additional resources that you think I should be looking at as well. In the meantime, I'll keep searching online and trying solutions.
EDIT 1
After discussing the problem with #Nir Friedman, I have come to appreciate that breaking implicit conversion rules and achieving an exact match is not what I want. So long as the passed type can be converted, it should be fine. I would appreciate a pointer on how to achieve this.
EDIT 2
I'm marking this question as closed because #Sam Varshavchik answered the exact question I asked. If anyone is interested in the question posed in EDIT 1, I'll either ask it as a separate question, or post my solution here for reference.
You clarified that your operator() returns a void, and you want to strictly match the signature, ignoring type conversions.
If that's so, then your expected results should be false true false false false false, and not true false false false false false:
is_callable_oper<has, int&>::value
Since has::operator() does not take a int & const & parameter, which collapses to an int &, the result of this test must be false.
is_callable_oper<has, int>
Since has does, indeed, have an operator() that takes a const int & parameter, this test should pass.
My solution simply uses std::is_same to compare two types, and std::enable_if to SFINAE-fail a template resolution candidate.
#include <type_traits>
#include <iostream>
struct has
{
void operator()(int const&);
};
struct hasNot1
{
void operator()(int);
};
struct hasNot2
{
void operator()();
};
struct hasNot3
{
void operator()(float);
};
struct hasNot4
{
};
template<typename T, typename P, typename foo=void>
class is_callable_oper : public std::false_type {};
template<typename T, typename P>
class is_callable_oper<T, P, typename std::enable_if<
std::is_same<decltype(&T::operator()),
void (T::*)(const P &)>::value>::type>
: public std::true_type {};
int main() {
std::cout << std::boolalpha << is_callable_oper<has, int&>::value << " "
<< is_callable_oper<has, int>::value << " "
<< is_callable_oper<hasNot1, int&>::value << " "
<< is_callable_oper<hasNot2, int&>::value << " "
<< is_callable_oper<hasNot3, int&>::value << " "
<< is_callable_oper<hasNot4, int&>::value << std::endl;
return 0;
}
EDIT: By using std::void_t, or a reasonable facsimile, in the specialization it should also be possible to make this work with overloaded operators:
template<typename T, typename P>
class is_callable_oper<T, P,
std::void_t<decltype( std::declval< void (T::*&)(const P &)>()=&T::operator())>>
: public std::true_type {};
On more modern compilers, the typical approach for this kind of problem is the void_t idiom: How does `void_t` work. Here's how it goes. Step 1:
template <class T>
using void_t = void; // avoid variadics for simplicity
Obviously this is general and not specific to your problem but it just doesn't happen to be in the standard library until 17.
Next, we define our trait and give the default detection as false:
template <class T, class P, class = void>
struct is_callable_oper : std::false_type {};
Now, we defined a specialized form that will work out if our function has the operator we want.
template <class T, class P>
struct is_callable_oper<T, P,
void_t<decltype(std::declval<T>()(std::declval<P>()))>>: std::true_type {};
The basic idea is that void_t as we know will always translate to void. But, the type being input will not make any sense unless the expression is valid; otherwise it will cause substitution failure. So basically, the code above will check whether
std::declval<T>()(declval<P>())
Is sensible for your type. Which is basically the trait detection that you want. Note that there are some subtleties involving lvalues, rvalues, constness, but that would be a bit involved to get into here.
Now, as Sam notes in the comments, whether this expression is sensible or not includes implicit conversions. So the output indeed is true true true false true false. Working example: http://coliru.stacked-crooked.com/a/81ba8b208b90a2ba.
For instance, you expected is_callable_oper<has, int>::value to be false. However, clearly one can call a function accepting const int& with an int. So instead you get true.
I am confused about the second parameter of std::enable_if.
In using of a return type of int, we can make it using:
template <class T>
typename std::enable_if<mpi::is_builtin<T>::value, int>::type
foo() { return 1; }
But how can I use enable_if in paramter or template? In this case, what's the difference of too functions below:
template<class T ,
class = typename std::enable_if<std::is_integral<T>::value>::type >
T too(T t) { std::cout << "here" << std::endl; return t; }
int too(int t) { std::cout << "there" << std::endl; return t; }
Thanks.
It means that in case of
template<class T ,
class = typename std::enable_if<std::is_integral<T>::value>::type >
it becomes
template<class T ,
class = void >
if the condition std::is_integral<T>::value is true, hence the function is allowed for the type T and therefore participates in overload resolution.
If the condition is not met, it becomes illegal and the typename std::enable_if<...>::type invalidates the function for the type T. In your example, the first method allows all integral types (int, unsigned, long, ...) but no classes, etc.
The second, int-only version in your example would loose some information and convert values from unsigned to signed or narrow some values, which is why the first version can be really helpful in some cases.
Note that void is actually the default for the second parameter of std::enable_if, which is often sufficient to enable or disable templates, etc. as you don't really need a specific type. All you need to know/detect is, whether or not it is valid (void) or invalid, in which case there is no valid substitution for the ::type part.
what's the difference of too functions below:
One is a template that can be called for any CopyConstructible type, the enable_if only constrains it when the default template argument is used:
#include <iostream>
template<class T ,
class = typename std::enable_if<std::is_integral<T>::value>::type >
T too(T t) { std::cout << "here" << std::endl; return t; }
int too(int t) { std::cout << "there" << std::endl; return t; }
int main()
{
too<double, void>(1.0);
}
I have a boost::variant and I would like to execute a functor only if the variant is of a special type, so I made up this function:
template<typename T, typename Variant>
void if_init(Variant& opt_variant, std::function<void(T)> functor){
if(auto* ptr = boost::get<T>(&opt_variant)){
functor(*ptr);
}
}
This works well, but I would like the type T to be deduced, so that I can write that:
if_init(b, [](double var){ std::cout << "I'm double and set" << std::endl; });
But the type is not deduced:
type_inference.cpp:19:5: error: no matching function for call to 'if_init'
if_init(b, [](double var){ std::cout << "I'm double and set" << std::endl; });
^~~~~~~
type_inference.cpp:10:6: note: candidate template ignored: failed template argument deduction
void if_init(Variant& opt_variant, std::function<void(T)> functor){
If I write:
if_init<double>(b, [](double var){ std::cout << "I'm double and set" << std::endl; });
it works well.
Is there a way to have type T being deduced ? I would like to type T only once. Here the type is short, but in the real case, there are long types.
I'm using CLang 3.2.
Here is the full test case (the first call compiles not the second):
#include <iostream>
#include <functional>
#include <boost/variant.hpp>
typedef boost::variant<int, double> Test;
template<typename T, typename Variant>
void if_init(Variant& opt_variant, std::function<void(T)> functor){
if(auto* ptr = boost::get<T>(&opt_variant)){
functor(*ptr);
}
}
int main(){
Test b = 1.44;
if_init<double>(b, [](double var){ std::cout << "I'm double and set" << std::endl; });
if_init(b, [](int var){ std::cout << "I'm int and set" << std::endl; });
return 0;
}
I recommend you think of std::function<Sig> as a container of any one functor that conforms to Sig as a signature -- and which can be replaced at any moment. This functionality comes in very handy for e.g. std::vector<std::function<Sig>> because such a container can then hold functors of different types.
In your case, because you only care to have just the one functor you really don't need the functionality of std::function<Sig>. As such, I recommend you declare your function template like so:
template<typename T, typename Variant, typename Functor>
void if_init(Variant& opt_variant, Functor functor);
If you are worried that this doesn't communicate that Functor must conform to a void(T) signature, please note that std::function<Sig> does not enforce that either: although obviously you will end up with a compilation error, it is not a nice one. It's planned to be changed (and maybe your implementation has that either), but changed to a different kind of error. Still not that helpful for your case.
I personally make use of template aliases (in the template parameter list) to both document and enforce what a functor should conform to. This ends up looking like:
// Documents that e.g. long l = std::forward<Functor>(functor)(42.)
// should be a valid expression -- a functor that returns int would
// also be accepted.
// Triggers a hard-error (typically a static_assert with a nice message)
// on violation.
template<typename Functor, Requires<is_callable<Functor, long(double)>>...>
R foo(Functor functor);
// Documents that this function template only participates in overload resolution
// if the functor conforms to the signature.
// Does not trigger a hard-error (necessary by design); if everything goes right
// then another overload should be picked up -- otherwise an error of the kind
// 'no matching overload found' is produced
template<typename Functor, EnableIf<is_callable<Functor, long(double)>>...>
R bar(Functor functor);
As to your exact question, the rules of C++ do not allow for a template parameter to be deduced in your case. It's really not an easily fixed 'problem', if it is one. You can find more information on this.