It seems that substitution failure sometimes is an error.
Could someone tell me when it will be an error and when it will not?
See run result here
Thank you!
Thank you for the informative answer!
This code doens't compile with g++ 4.8, but works as expected with clang++ 3.2, g++ 4.7.3 and intel 13.0.1.
So now I'm sure this is a bug to g++ 4.8. I have reported this to gcc bugzila.
In substitution of ‘template<class C> static constexpr int has<T>::test(decltype (sizeof (C:: x))) [with C = C; T = foo] [with C = foo]’:
required from ‘const int has<foo>::value’
required from here
error: invalid use of non-static member function ‘std::string foo::x()’
The code
template <typename T>
struct has {
template <typename>
constexpr static int test(...) {
return 0;
}
template <typename C>
constexpr static int test(decltype(sizeof(C::x))) { // Doesn't compile.
return 1; // Is a member variable.
}
template <typename C, int c =
sizeof(decltype(((C*)nullptr)->x()))>
constexpr static int test(int) {
return 2; // Is a member function.
}
static const int value = test<T>(0);
};
struct foo {
string x();
};
struct bar {
string x;
};
int main() {
std::cout << has<int>::value << std::endl;
std::cout << has<foo>::value << std::endl;
std::cout << has<bar>::value << std::endl;
}
The rule is essentially that the error must depend on a template parameter of the declaration which it immediately belongs to.
This example looks like a compiler bug. It doesn't correctly not-an-error out the particular syntax flaw.
If you want it to return success when the class has a nonstatic member function named x, you should use &C::x, because you're allowed to take a pointer to a member function but not simply to name it as a standalone subexpression.
My preferred way to do this would be
template< typename, typename = void >
struct has_x
: std::false_type {};
template< typename t >
struct has_x< t, typename std::enable_if< & t::x == & t::x >::type >
: std::true_type {};
Related
I am using this variant library: https://github.com/cbeck88/strict-variant. It provides a class similar to std::variant and boost::variant. Given this struct:
struct S
{
explicit S(double) {}
};
I want to do this:
strict_variant::variant<double, S> v = 2.0;
This works with Clang 5.0.1 and MSVC 19.12.25831.00, but fails to compile with GCC 7.2.1.
I looked at the library's code and reduced the problem to this:
#include <iostream>
struct S
{
constexpr S() {}
constexpr explicit S(double) {}
};
template<unsigned i> struct init_helper;
template<> struct init_helper<0> { using type = double; };
template<> struct init_helper<1> { using type = S; };
template<unsigned i>
struct initializer_leaf
{
using target_type = typename init_helper<i>::type;
constexpr unsigned operator()(target_type) const
{
return i;
}
};
struct initializer : initializer_leaf<0>, initializer_leaf<1>
{
};
int main()
{
std::cout << initializer()(double{}) << " = double" << '\n';
std::cout << initializer()(S{}) << " = S" << '\n';
return 0;
}
with the output being
0 = double
1 = S
GCC says:
strict_variant_test.cpp: In function ‘int main()’:
strict_variant_test.cpp:29:37: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(double{}) << " = double" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
strict_variant_test.cpp:30:32: error: request for member ‘operator()’ is ambiguous
std::cout << initializer()(S{}) << " = S" << '\n';
^
strict_variant_test.cpp:17:21: note: candidates are: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 1; initializer_leaf<i>::target_type = S]
constexpr unsigned operator()(target_type) const
^~~~~~~~
strict_variant_test.cpp:17:21: note: constexpr unsigned int initializer_leaf<i>::operator()(initializer_leaf<i>::target_type) const [with unsigned int i = 0; initializer_leaf<i>::target_type = double]
But, it works with GCC (and still Clang and MSVC) when I change the definition of initializer to this:
struct initializer
{
constexpr unsigned operator()(double) const
{
return 0;
}
constexpr unsigned operator()(S) const
{
return 1;
}
};
My understanding of C++ says that this is equivalent, so I assume that this is a bug in GCC, but I have often run into problems where the standard says surprising things and my assumption is wrong. So, my question is: whose fault is this? Does GCC have a bug, do Clang and MSVC have a bug, or is the interpretation of the code undefined/unspecified such that all compilers are right? If the code is wrong, how can it be fixed?
This is actually a clang bug.
The rule of thumb is that the names in different scopes don't overload. Here's a reduced example:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{};
int main()
{
Derived<int, double>().foo(0); // error
}
This should be an error because the class member lookup rules state that basically only one base class can contain a given name. If more than one base class has the same name, lookup is ambiguous. The resolution here is to bring both base class names into the derived class with a using-declaration. In C++17, that using declaration can be a pack expansion, which makes this problem a whole lot easier:
template <typename T>
class Base {
public:
void foo(T ) { }
};
template <typename... Ts>
struct Derived: Base<Ts>...
{
using Base<Ts>::foo...;
};
int main()
{
Derived<int, double>().foo(0); // ok! calls Base<int>::foo
}
For the specific library, this code:
template <typename T, unsigned... us>
struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... {
static_assert(sizeof...(us) > 0, "All value types were inelligible!");
};
should look like:
template <typename T, unsigned... us>
struct initializer_base<T, mpl::ulist<us...>> : initializer_leaf<T, us>... {
static_assert(sizeof...(us) > 0, "All value types were inelligible!");
using initializer_leaf<T, us>::operator()...; // (*) <==
};
(although I guess the library is targeting C++11, so I submitted a C++11-compliant fix for it... which is just a bit more verbose).
While trying to solve Is it possible to tell if a class has hidden a base function in C++?, I generated this:
#include <type_traits>
#include <iostream>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;
template<class T, class B>
auto has_x_f(B*) -> std::false_type;
template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));
template<typename T>
struct A
{
void x() {}
static const bool x_hidden;
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
void y(R value)
{
std::cout << "x() is not hidden" << std::endl;
}
//using t = std::integral_constant<bool, x_hidden>;
};
struct B : A<B>
{
void x() {}
};
struct C : A<C>
{
};
template<typename T>
const bool A<T>::x_hidden = has_x<T, A<T>>::value;
int main()
{
B b;
C c;
std::cout << "B: ";
std::cout << b.x_hidden << std::endl;
std::cout << "C: ";
std::cout << c.x_hidden << std::endl;
std::cout << "B: ";
b.y(b);
std::cout << "C: ";
c.y(c);
return 0;
}
Which outputs what I want:
B: 1
C: 0
B: x() is hidden
C: x() is not hidden
clang and gcc both compile and execute this "correctly", but vc++ doesn't (though I am aware that there are problems with it working properly with expressions similar to template <typename T> ... decltype(fn(std::declval<T>().mfn()))).
So my question is, is this considered valid or will it break later on? I'm also curious about the x_hidden being able to be used as a template parameter in the functions but not being able to use it in using t = std::integral_constant<bool, x_hidden>. Is that just because the template's type isn't fully declared at this point? If so, why did using it work for the function declarations?
If x_hidden is false, there is no template arguements for which this template function
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value) {
std::cout << "x() is hidden" << std::endl;
}
can be instantiated, so your program is ill formed no diagnostic required. This is a common hack, its illegality may be made clear or even legal at some point.
There may be a reason for using has_x_f instead of just directly initializing is_hidden with the is_same clause, but it isn't demonstrated in your code.
For any template specialization, there must be arguments which would make the instantiation valid. If there are not, the program is ill-formed no diagnostic required.
I believe this clause is in the standard to permit compilers to do more advanced checks on templates, but not require them.
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
the compiler is free to notice x_hidden is false, and say "it doesn't matter what is_same<T,R> is", and deduce that no template arguments could make this specialization valid. Then generate an error.
An easy hack is
template <class T2=T, class R,
ENABLE_IF(std::is_same<T2, R>::value && has_x<T2, A<T2>>::value)
>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
where we sneak another template argument in that equals T usually. Now, the compiler has to admit the possibility that T2 passes the has_x test, and that the passed argument is R. Users can bypass this by manually passing the "wrong" T2.
This may not solve everything. The standard is a bit tricky to read here, but one reading states that if within the body of y() we go and assume that our T itself has x(), we still violate the rule of the possibility of a valid template instantiation.
[temp.res] 14.6/8 (root and 1)
Knowing which names are type names allows the syntax of every template to be checked. The program is ill-formed, no diagnostic required, if:
no valid specialization can be generated for a template [...] and the template is not instantiated, or
No valid specialization for
template <typename R, ENABLE_IF(std::is_same<T, R>::value && x_hidden)>
void y(R value)
{
std::cout << "x() is hidden" << std::endl;
}
can be generated if x_hidden is false. The exitence of another overload is immaterial.
If you fix it using the T2 trick, the same rule holds if the body assumes T=T2.
Three are words in the standard that attempt to not cause the template to be instantiated in certain contexts, but I am unsure if that makes the above code well formed or not.
I tried compiling your code with the Intel C++ compiler(icpc (ICC) 17.0.2 20170213), and it would not compile with the following message:
main.cpp(30): error: expression must have a constant value
template <typename R, ENABLE_IF(std::is_same<T, R>::value && !x_hidden)>
^
/home/com/gcc/6.2.0/bin/../include/c++/6.2.0/type_traits(2512): error: class "std::enable_if<<error-constant>, int>" has no member "type"
using enable_if_t = typename enable_if<_Cond, _Tp>::type;
^
detected during instantiation of type "std::enable_if_t<<error-constant>, int>" at line 30 of "main.cpp"
main.cpp(62): error: more than one instance of overloaded function "B::y" matches the argument list:
function template "void A<T>::y(R) [with T=B]"
function template "void A<T>::y(R) [with T=B]"
argument types are: (B)
object type is: B
b.y(b);
I was however able to compile the following with both the Intel compiler and GCC.
#include <iostream>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class B, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f(T*) -> std::true_type;
template<class T, class B>
auto has_x_f(B*) -> std::false_type;
template<class T, class B>
using has_x = decltype(has_x_f<T, B>((T*)nullptr));
template<class T>
class A
{
public:
T& self() { return static_cast<T&>(*this); }
void x() { }
template
< class TT = T
, typename std::enable_if<has_x<TT, A<TT> >::value, int>::type = 0
>
void y()
{
std::cout << " have x hidden " << std::endl;
// if you are so inclined, you can call x() in a "safe" way
this->self().x(); // Calls x() from class "Derived" (Here class B)
}
template
< class TT = T
, typename std::enable_if<!has_x<TT, A<TT> >::value, int>::type = 0
>
void y()
{
std::cout << " does not have x hidden " << std::endl;
// if you are so inclined, you can call x() in a "safe" way
this->self().x(); // Calls x() from class "Base" (Here class A)
}
};
class B : public A<B>
{
public:
void x() { }
};
class C : public A<C>
{
};
int main()
{
B b;
C c;
b.y();
c.y();
return 0;
}
I am not aware whether or not this is incorrect according to the standard however, but as I see it you do not run into the problem mentioned in one of the other answers, that you have a template that cannot be instantiated.
EDIT: I was able to get to compile on MSVC 2017 by some "old-times" template metaprogramming tricks, and using classes instead of functions.
If I use this implementation of has_x instead it compiles:
template<class T, bool>
struct has_x_impl;
template<class T>
struct has_x_impl<T, true>: std::true_type
{
};
template<class T>
struct has_x_impl<T, false>: std::false_type
{
};
template<class T>
using has_x = has_x_impl<T, std::is_same<void(T::*)(), decltype(&T::x)>::value>;
Full code on Wandbox here.
I had a bit of a code clean-up (got rid of the out-of-line x_hidden declaration) and ended up with the following. I also fixed it slightly based on #Yakk's answer above, to avoid [temp.res]/8 invalidating it.
#include <type_traits>
#include <iostream>
#include <cassert>
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), int> = 0
template<class T, class Base, ENABLE_IF(std::is_same<void(T::*)(), decltype(&T::x)>::value)>
auto has_x_f() -> std::true_type;
template<class T, class Base, ENABLE_IF(std::is_same<void(Base::*)(), decltype(&T::x)>::value)>
auto has_x_f() -> std::false_type;
template<class T, class Base>
using has_x = decltype(has_x_f<T, Base>());
template<typename T>
struct A
{
void x() {}
static bool constexpr x_hidden() {
return has_x<T, A<T>>::value;
}
void y()
{
assert(x_hidden() == y_<T>(nullptr) );
}
void y2()
{
if constexpr(x_hidden()) {
typename T::BType i = 1;
(void)i;
} else {
typename T::CType i = 1;
(void)i;
}
}
private:
template <typename R, typename T2=T, ENABLE_IF(A<T2>::x_hidden())>
static bool y_(R*)
{
std::cout << "x() is hidden" << std::endl;
return true;
}
template <typename R, typename T2=T, ENABLE_IF(!A<R>::x_hidden())>
static bool y_(T*)
{
std::cout << "x() is not hidden" << std::endl;
return false;
}
};
struct B : A<B>
{
void x() {}
using BType = int;
};
static_assert(std::is_same<decltype(&B::x), void(B::*)()>::value, "B::x is a member of B");
struct C : A<C>
{
using CType = int;
};
static_assert(std::is_same<decltype(&C::x), void(A<C>::*)()>::value, "C::x is a member of A<C>");
int main()
{
B b;
C c;
std::cout << "B: ";
std::cout << B::x_hidden() << std::endl;
std::cout << "C: ";
std::cout << C::x_hidden() << std::endl;
std::cout << "B: ";
b.y();
b.y2();
std::cout << "C: ";
c.y();
c.y2();
return 0;
}
Live demo on wandbox -- gcc and clang are both happy with it.
MSVC 2017 complained
error C2064: term does not evaluate to a function taking 0 arguments
for both uses of A<T2>::x_hidden(), when instantiating A<B> for B to inherit from.
MSVC 2015 gave the same complaint, and then suffered an Internal Compiler Error. ^_^
So I think this is valid, but exercises MSVC's constexpr or template instantiation machinery in unpleasant ways.
Per the example in [expr.unary.op]/3, the type of &B::x is void (B::*)(), and the type of &C::x is void (A<C>::*)(). So the first has_x_f() will be present when T is B, and the second has_x_f() will be present when T is C and Base is A<C>.
Per [temp.inst]/2, instantiating the class instantiates declarations but not definitions of the members. Per [temp.inst]/3 and 4, member function definitions (including template functions) are not instantiated until required.
Our declarations here are currently different, as the use of R and T2 mean the compiler cannot determine the truth or falsehood of either size of the &&.
The use of the different parameter types helps MSVC, which would otherwise see them as redefinitions of the same template member template function. My reading of [temp.inst]/2 says this is not needed, as they're only redefintions when we instantiate them, and they cannot be instantiated at the same time. Because we use A<T2>::x_hidden() and !A<R>::x_hidden(), the compiler cannot know that they are mutually exclusive at this time. I don't think it's necessary to do that to avoid [temp.res]/8, simply using A<R>::x_hidden() seems safe-enough to me. This was also to ensure that in the two templates, R as actually used.
From there on, it's pretty easy. y() shows we have the right values coming from both paths.
Depend on your use-case, you could use if constexpr with x_hidden() to avoid all the template magic in y_(), per y2() above.
This avoids the issue with [temp.res]/8 described in #Yakk's answer, as the problematic clause [temp.res]/8.1 is that the template is ill-formed if
no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, [...]
So as long as you instantiate A<T>::y2() for some T, then you're not subject to this clause.
The y2() approach has the advantage of working with MSVC2017, as long as you pass in the "/std:c++latest" compiler flag.
How to specialize a template function for the case that the value of one of its argument is known/unknown during compile time (before actually compile and run the program)?
I can't figure out how yet.
idea 1:
#include <type_traits>
#include <iostream>
int main(void){
int a; //value of a is not known at compile time
bool b = (a == a); //value of b is known at compile time.
std::is_assignable< constexpr bool, bool >::value
}
//g++ magic.cpp -std=c++14
//error: wrong number of template arguments (1, should be 2)
// std::is_assignable< constexpr bool, bool >::value
Idea 2:
#include <type_traits>
#include <iostream>
int main(void){
const int a=1;
int b = (a == a);
std::cout << __builtin_constant_p (a) << std::endl;
std::cout << __builtin_constant_p (b) << std::endl;
}
//prints 0 and 0.
Well, I guess you mean the type of the argument, right? Values don't matter for partial template specializations...
Then: This can't be done.
Parameter types for templates must be known at compile time. How else should the compiler produce the correct code?
Also for partial template specializations, the types must be known at compile time for the same reason.
I'm a bit late to the party, and my partial answer may not be very satisfying, but here goes:
Situations where we can't tell from inspection whether a value is known at compile time:
Non-template input values to constexpr functions
Members of types provided by template arguments
I don't know what to do about problem 1, but for problem 2 we can use SFINAE: looking for a specific member with known name (in the below example it's X), and trying to send it as a template argument, like so:
// like std::void_t, but for value inputs:
template <auto>
using v_to_void_t = void;
//////////////////////////////////////////////////////////////
template <class, class = void>
struct has_constexpr_X
: std::false_type {};
template <class T>
struct has_constexpr_X <T, v_to_void_t<T().X>>
: std::true_type {};
template <class T>
constexpr bool has_constexpr_X_v
= has_constexpr_X<T>::value;
Example uses:
struct HasStaticConstexprX {
static constexpr int X = 2;
};
struct HasStaticConstX {
static const int X; // implied constexpr
};
const int HasStaticConstX::X = 3;
struct HasStaticX {
static int X;
};
int HasStaticX::X = 4;
struct HasConstX {
const int X;
};
int main () {
static_assert(has_constexpr_X_v<HasStaticConstexprX>);
static_assert(has_constexpr_X_v<HasStaticConstX>);
static_assert(! has_constexpr_X_v<HasStaticX>);
static_assert(! has_constexpr_X_v<HasConstX>);
}
Demos:
c++17
c++14
I got some problem with a static constexpr member of a template struct. The code compiles but I get linking error. Here's what I'm trying to do:
template<int n>
struct Test {
static constexpr auto invoke = make_tuple(2, "test", 3.4);
};
template<typename T>
void test(T&& t) {
cout << t << endl;
}
int main() {
test(get<0>(Test<2>::invoke));
return 0;
}
I got linking errors with that, so I tried this:
template<int n>
struct Test {
static constexpr auto invoke = make_tuple(2, "test", 3.4);
};
// declare it outside the class
template<int n>
constexpr decltype(Test<n>::invoke) Test<n>::invoke;
template<typename T>
void test(T&& t) {
cout << t << endl;
}
int main() {
test(get<0>(Test<2>::invoke));
return 0;
}
But instead I got this strange error:
error: redefinition of 'invoke' with a different type: 'const decltype(Test<n>::invoke)' vs 'const std::tuple<int, const char *, double>'
A different type??
Obviously, the non-template version works just fine:
struct Test {
static constexpr auto invoke = make_tuple(2, "test", 3.4);
};
constexpr decltype(Test::invoke) Test::invoke;
template<typename T>
void test(T&& t) {
cout << t << endl;
}
int main() {
test(get<0>(Test::invoke));
return 0;
}
How can I get the template version to work? Thank you very much
It looks like you are running into an interesting corner case with decltype, this is covered in the clang bug report Static constexpr definitions used inside template which has the following example with a similar error as yours:
This compiles fine, but when I make the class A, a template like this:
struct L
{
void operator()() const
{}
};
template<class X>
struct A
{
static constexpr auto F = L();
};
template<class X>
constexpr decltype(A<X>::F) A<X>::F;
int main()
{
A<void>::F();
return 0;
}
Clang crashes, if I change the definition line for F to this:
template<class X>
constexpr typename std::remove_const<decltype(A<X>::F)>::type A<X>::F;
Then clang produces this error:
error: redefinition of 'F' with a different type
constexpr typename std::remove_const<decltype(A<X>::F)>::type A<X>::F;
^
note: previous definition is here
static constexpr auto F = L();
^
and Richard Smith's reply was as follows:
This error is correct. 'constexpr' and 'auto' are red herrings here.
Reduced testcase:
template<class X> struct A { static int F; };
template<class X> decltype(A<X>::F) A<X>::F;
Per C++11 [temp.type]p2, "If an expression e involves a template
parameter, decltype(e) denotes a unique dependent type." Therefore the
type of A::F does not match the type in the template.
the full quote for that from the C++14 draft is as follows:
If an expression e involves a template parameter, decltype(e) denotes
a unique dependent type. Two such decltype-specifiers refer to the
same type only if their expressions are equivalent (14.5.6.1). [ Note:
however, it may be aliased, e.g., by a typedef-name. —end note ]
The only obvious way I can see to get this work is:
template<int n>
constexpr decltype(make_tuple(2, "test", 3.4)) Test<n>::invoke;
No work around was offered in the bug report.
How can I get the template version to work?
FWIW, your method works fine on my desktop, which uses g++ 4.8.4.
You can use:
template<int n>
constexpr decltype(make_tuple(2, "test", 3.4)) Test<n>::invoke;
The following also works on my desktop:
template<int n>
struct Test {
static constexpr auto invoke = make_tuple(2, "test", 3.4);
typedef decltype(invoke) invoke_t;
};
template<int n>
constexpr typename Test<n>::invoke_t Test<n>::invoke;
I am trying to write a template class which may or may not define a particular member function depending on its template parameter type. Further the return type of this member function depends on the return type of of a member of the template paramter (if defined).
Below is a minimal example of my code
#include <iostream>
#include <type_traits>
template <typename T>
struct has_foo_int {
private:
template <typename U>
static decltype(std::declval<U>().foo(0), void(), std::true_type()) test(int);
template <typename>
static std::false_type test(...);
public:
typedef decltype(test<T>(0)) test_type;
enum { value = test_type::value };
};
template <typename T, bool HasFooInt>
struct foo_int_return_type;
template<typename T>
struct foo_int_return_type<T,false> {};
template<typename T>
struct foo_int_return_type<T,true> {
using type = decltype(std::declval<T>().foo(0));
};
template<typename T>
struct mystruct
{
T val;
//auto someMethod(int i) -> decltype(std::declval<T>().foo(0)) // error: request for member ‘foo’ in ‘std::declval<double>()’, which is of non-class type ‘double’
//auto someMethod(int i) -> typename foo_int_return_type<T,has_foo_int<T>::value>::type // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
template<typename R=typename foo_int_return_type<T,has_foo_int<T>::value>::type> R someMethod(int i) // error: no type named ‘type’ in ‘struct foo_int_return_type<double, false>’
{
return val.foo(i);
}
};
struct with_foo_int {
int foo(int i){
return i+1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
return 0;
}
What I would like to happen is that the code compiles fine and outputs 42 for ms1.someFunc(41). I would also expect that if one accidentally tried to call someFunc on ms2 that it would fail to compile.
Unfortunately each of the alternatives I have tried has failed. The first and second, I think I understand why they wouldn't work.
I read here that SFINAE only works for template functions so I tried giving a dummy template parameter to work out the return type but this too fails in the same way.
I'm clearly not understanding something here, what am I missing? Is it possible to achieve what I'm trying to do?
Thanks.
P.s. I'm using g++ 4.7.3
P.p.s I have also tried std::enable_if but get much the same results as with my foo_int_return_type struct.
Here is a short, tidy and documented way of doing what you are attempting,
with some possible bugs addressed thereafter.
#include <type_traits>
/*
Template `has_mf_foo_accepts_int_returns_int<T>`
has a static boolean public member `value` that == true
if and only if `T` is a class type that has a public
member function or member function overload
`int T::foo(ArgType) [const]` where `ArgType`
is a type to which `int` is implicitly convertible.
*/
template <typename T>
struct has_mf_foo_accepts_int_returns_int {
/* SFINAE success:
We know now here `int *` is convertible to
"pointer to return-type of T::foo(0)"
*/
template<typename A>
static constexpr bool test(
decltype(std::declval<A>().foo(0)) *prt) {
/* Yes, but is the return-type of `T::foo(0)`
actually *the same* as `int`?...
*/
return std::is_same<int *,decltype(prt)>::value;
}
// SFINAE failure :(
template <typename A>
static constexpr bool test(...) {
return false;
}
/* SFINAE probe.
Can we convert `(int *)nullptr to
"pointer to the return type of T::foo(0)"?
*/
static const bool value = test<T>(static_cast<int *>(nullptr));
};
template<typename T>
struct mystruct
{
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
T val;
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` == `int` and `has_good_foo` == true.
*/
template<typename R = int>
typename std::enable_if<
(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
return val.foo(i);
}
/* SFINAE:
`template<typename R> R someMethod(R)` will be this if and only
if `R` != `int` or `has_good_foo` != true.
*/
template<typename R = int>
typename std::enable_if<
!(has_good_foo::value && std::is_same<R,int>::value),R
>::type
someMethod(R i) {
static_assert(has_good_foo::value && std::is_same<R,int>::value,
"mystruct<T> does not implement someMethod(R)");
return i;
}
};
// Testing...
#include <iostream>
struct with_foo_int
{
int foo(int i) {
return i + 1;
}
};
using namespace std;
int main(void)
{
mystruct<with_foo_int> ms1;
cout << ms1.someMethod(41) << endl;
mystruct<double> ms2;
cout << ms2.someMethod(41) << endl; // static_assert failure
return 0;
}
This solution faithfully reproduces a couple of possible loopholes in your
own attempt as posted:-
1) It looks as if you may believe that evaluating std::declval<U>().foo(0) is
a SFINAE way of determining whether U::foo exists and takes a single argument
of type int. It doesn't. It is merely a SFINAE way of determining whether
U::foo(ArgType) exists where ArgType is anything to which 0 is
implicitly convertible. Thus ArgType could be any pointer-or-arithmetic
type, not just int.
2) You may not have considered that std::declval<U>().foo(0) will be satisfied
if either or both of U::foo(ArgType) U::foo(ArgType) const exists. You
may well care whether you call a const or a non-const member function on
U, and you would certainly care which of two member function you call. If
with_foo_int were defined as:
struct with_foo_int
{
int foo(int i) const {
return i + 1;
}
int foo(int i) {
return i + 2;
}
};
then the solution given would call the non-const overload and
ms1.someMethod(41) would == 43.
2) Is easily dealt with. If you wish to ensure that you can only call
T::foo(ArgType) const then add a const qualifier to mystruct::someMethod.
If you don't care or wish only to call T::foo(ArgType) then leave things
as they are.
1) is a little harder to solve, because you must craft a SNIFAE probe for
T::foo that is satisfied only if it has the right signature, and that
signature will either be const qualified or not. Let's assume you want
int T::foo(int) const. In that case, replace template
has_mf_foo_accepts_int_returns_int with:
/* Template `has_mf_foo_arg_int_returns_int<T>
has a static boolean public member `value` that == true
if and only if `T` is a class type that has an un-overloaded
a public member `int T::foo(int) const`.
*/
template< typename T>
struct has_mf_foo_arg_int_returns_int
{
/* SFINAE foo-has-correct-sig :) */
template<typename A>
static std::true_type test(int (A::*)(int) const) {
return std::true_type();
}
/* SFINAE foo-exists :) */
template <typename A>
static decltype(test(&A::foo))
test(decltype(&A::foo),void *) {
/* foo exists. What about sig? */
typedef decltype(test(&A::foo)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
and in template mystruct replace:
using has_good_foo = has_mf_foo_accepts_int_returns_int<T>;
with:
using has_good_foo = has_mf_foo_arg_int_returns_int<T>;
(Template has_mf_foo_arg_int_returns_int is adapted
from my other answer and
you can read how it works there.)
What you gain in SFINAE-precision from the latter approach comes at
a price. The approach requires you to attempt to take the address of T::foo,
to see if it exists. But C++ will not give you the address of an overloaded
member function, so this approach will fail if T::foo is overloaded.
The code here will compile (or appropriately static_assert) with
GCC >= 4.7.2 clang >= 3.2.