Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -283,6 +283,32 @@ ptr = &i; // OK: 'TrueConstant' is a truthy constant ptr = &j; // error: 'FalseConstant' is a constant, but not truthy } + +Because ``enable_if`` evaluation happens during overload resolution, +``enable_if`` may give unintuitive results when used with templates, depending +on when overloads are resolved. In the example below, clang will emit a +diagnostic about no viable overloads for ``foo`` in ``bar``, but not in ``baz``: + +.. code-block:: c++ + + double foo(int i) __attribute__((enable_if(i > 0, ""))); + void *foo(int i) __attribute__((enable_if(i <= 0, ""))); + template + auto bar() { return foo(I); } + + template + auto baz() { return foo(T::number); } + + struct WithNumber { constexpr static int number = 1; }; + void callThem() { + bar(); + baz(); + } + +This is because, in ``bar``, ``foo`` is resolved prior to template +instantiation, so the value for ``I`` isn't known (thus, both ``enable_if`` +conditions for ``foo`` fail). However, in ``baz``, ``foo`` is resolved during +template instantiation, so the value for ``T::number`` is known. }]; } Index: lib/Sema/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -5984,7 +5984,6 @@ SFINAETrap Trap(*this); SmallVector ConvertedArgs; bool InitializationFailed = false; - bool ContainsValueDependentExpr = false; // Convert the arguments. for (unsigned I = 0, E = Args.size(); I != E; ++I) { @@ -6006,7 +6005,6 @@ break; } - ContainsValueDependentExpr |= R.get()->isValueDependent(); ConvertedArgs.push_back(R.get()); } @@ -6027,7 +6025,6 @@ InitializationFailed = true; break; } - ContainsValueDependentExpr |= R.get()->isValueDependent(); ConvertedArgs.push_back(R.get()); } @@ -6037,18 +6034,14 @@ for (auto *EIA : EnableIfAttrs) { APValue Result; - if (EIA->getCond()->isValueDependent()) { - // Don't even try now, we'll examine it after instantiation. - continue; - } - + // FIXME: This doesn't consider value-dependent cases, because doing so is + // very difficult. Ideally, we should handle them more gracefully. if (!EIA->getCond()->EvaluateWithSubstitution( - Result, Context, Function, llvm::makeArrayRef(ConvertedArgs))) { - if (!ContainsValueDependentExpr) - return EIA; - } else if (!Result.isInt() || !Result.getInt().getBoolValue()) { + Result, Context, Function, llvm::makeArrayRef(ConvertedArgs))) + return EIA; + + if (!Result.isInt() || !Result.getInt().getBoolValue()) return EIA; - } } return nullptr; } Index: test/SemaCXX/enable_if.cpp =================================================================== --- test/SemaCXX/enable_if.cpp +++ test/SemaCXX/enable_if.cpp @@ -116,9 +116,9 @@ void g() { f(); } }; -int fn3(bool b) __attribute__((enable_if(b, ""))); +int fn3(bool b) __attribute__((enable_if(b, ""))); // FIXME: This test should net 0 error messages. template void test3() { - fn3(sizeof(T) == 1); + fn3(sizeof(T) == 1); // expected-error{{no matching function for call to 'fn3'}} expected-note@-2{{candidate disabled}} } template @@ -138,7 +138,7 @@ void h(int); template void outer() { void local_function() __attribute__((enable_if(::h(T()), ""))); - local_function(); + local_function(); // expected-error{{no matching function for call to 'local_function'}} expected-note@-1{{candidate disabled}} }; namespace PR20988 { @@ -158,9 +158,9 @@ fn2(expr); // expected-error{{no matching function for call to 'fn2'}} } - int fn3(bool b) __attribute__((enable_if(b, ""))); + int fn3(bool b) __attribute__((enable_if(b, ""))); // FIXME: This test should net 0 error messages. template void test3() { - fn3(sizeof(T) == 1); + fn3(sizeof(T) == 1); // expected-error{{no matching function for call to 'fn3'}} expected-note@-2{{candidate disabled}} } } @@ -386,3 +386,34 @@ f.bar(1, 2); // expected-error{{too many arguments}} } } + +// Ideally, we should be able to handle value-dependent expressions sanely. +// Sadly, that isn't the case at the moment. +namespace dependent { +int error(int N) __attribute__((enable_if(N, ""))); // expected-note{{candidate disabled}} +int error(int N) __attribute__((enable_if(!N, ""))); // expected-note{{candidate disabled}} +template int callUnavailable() { + return error(N); // expected-error{{no matching function for call to 'error'}} +} + +constexpr int noError(int N) __attribute__((enable_if(N, ""))) { return -1; } +constexpr int noError(int N) __attribute__((enable_if(!N, ""))) { return -1; } +constexpr int noError(int N) { return 0; } + +template +constexpr int callNoError() { return noError(N); } +static_assert(callNoError<0>() == 0, ""); +static_assert(callNoError<1>() == 0, ""); + +template constexpr int templated() __attribute__((enable_if(N, ""))) { + return 1; +} + +constexpr int A = templated<0>(); // expected-error{{no matching function for call to 'templated'}} expected-note@-4{{candidate disabled}} +static_assert(templated<1>() == 1, ""); + +template constexpr int callTemplated() { return templated(); } + +constexpr int B = callTemplated<0>(); // expected-error{{initialized by a constant expression}} expected-error@-2{{no matching function for call to 'templated'}} expected-note{{in instantiation of function template}} expected-note@-9{{candidate disabled}} +static_assert(callTemplated<1>() == 1, ""); +}