narrowing-cast warnings in SFINAE evaluation - c++

I am getting Wnarrowing warnings produced from a SFINAE context when this code is used on gcc (specifically gcc<5.0 or if c++17 is enabled on gcc 7). Clang and MSVC do not produce those warnings on this code.
// check for constructibility from a specific type and copy assignable used in the parse detection
template <typename T, typename C> class is_direct_constructible {
template <typename TT, typename CC>
static auto test(int) -> decltype(TT{std::declval<CC>()}, std::is_move_assignable<TT>());
template <typename, typename> static auto test(...) -> std::false_type;
public:
static const bool value = decltype(test<T, C>(0))::value;
};
This is being used to check if we can construct a type from another type and then move into it from the newly constructed value.
I can disable the warnings around this code if need be but I would like to understand if there is something I can do differently in the code that wouldn't produce them in the first place.
The brace initialization is what is being done in the context this is used along with other conditions, but the warnings seem to be produced even if the overload that uses the brace initialization isn't chosen.
Werror is used in some contexts which causes a compilation failure otherwise this seems to compile and execute as I would expect, just with a bunch of warnings.
Say a class has a constructor
class classX
{
classX(char){}
...
}
then
is_direct_constructible<classX,int>::value
would evaluate to true but produce a warning on gcc.
Ideally narrowing conversions would produce a SFINAE failure but I haven't figured out any way to prevent that.
Short of that I need to get rid of the warnings around this code.
Given the restricted nature of what compilers produce the warnings, what is a way to work around them.

Related

Can a compiler decide to ignore a missing typename on dependent type where it is still required by C++20?

The following code compiles with MSVC but fails with GCC and Clang for missing typename before a dependent type:
struct Foo { struct value{ }; };
struct Bar { int value; };
template<typename T>
constexpr size_t SIZE = sizeof(T::value); // missing typename here, for Foo
constexpr size_t s1 = SIZE<Foo>; // missing typename before dependent type above
constexpr size_t s2 = SIZE<Bar>;
MSVC approach, not requiring typename for sizeof, seems reasonable, as sizeof works on both types and variables. On the other hand GCC and Clang seem to play by the book, as this is one of those cases where you still need typename even in C++20, when the context cannot reveal to the compiler whether it is going to meet a type or a variable.
The question is whether MSVC is allowed to be permissive here, that is, if the compiler can perform the required operation correctly without the typename, is it allowed to do so? Or does it contradict the specification?
The difference between the approach of MSVC vs. Clang and GCC comes to an actual difference in the following code, which compiles by all three compilers but behaves differently:
template<typename T> concept A1 = sizeof(typename T::value) > 0;
template<typename T> concept A2 = sizeof(T::value) > 0;
struct Foo { struct value{ }; };
constexpr bool v1 = A1<Foo>; // true with all
constexpr bool v2 = A2<Foo>; // true with MSVC, false with GCC and Clang
In the above code, GCC and Clang see the omission of typename in A2 as illegal and thus fail the concept, this is not compilation error but rather having the concept getting the boolean value false ([temp.constr.atomic]).
C++20 spec ([temp.res.general]) lists the places where typename is not required for assuming a qualified-id is a type. The sizeof operator is not in that list, so it seems that it should require typename for a template dependent-type. On the other hand, sizeof doesn't appear in the example as ill-formed, no diagnostic required for missing typename (not being in the example does not say anything, but still keeps the question, in a way).
Why sizeof may allow inferring proper operation without adding typename? Mainly because it shouldn't really care if this is a type or a variable. And the spec does not say specifically that this is ill-formed. If it does say that it is ill-formed, then a pointer to this should be the answer, which will probably make MSVC behavior a defect.
As a side note, that does not answer the question, one can make all three compilers happy with the following code:
template<typename T>
constexpr size_t SIZE = sizeof(T::value);
template<typename T> requires requires { typename T::value; }
constexpr size_t SIZE<T> = sizeof(typename T::value);
And for the concept:
template<typename T> concept A =
sizeof(T::value) > 0 ||
sizeof(typename T::value) > 0;
The standard is very indirect about this point, but it’s pretty clear that MSVC is not conforming here. (In the non-SFINAE/concept case, it would be allowed to issue merely a warning (“operand of sizeof considered a type despite missing ‘typename’”) and continue, but that’s not really the point.)
Note that even in the trivial case of
struct X;
int y=X;
the error is that X can’t be interpreted as an unqualified-id because it is not “suitably declared” ([expr.prim.id.unqual]/1). In a template the grammatical interpretation is fixed by the presence of absence of typename, despite the fact that qualified-ids are thereby produced without knowing whether their terminal names are so suitably declared; we evidently must reject them if eventually they are found wanting in that regard.

`requires` expression is evaluated to false in a nested template, but code is still compiled

I am failing to understand how the requires keyword works inside a nested template.
The code below can be compiled on the latest versions of MSVC and gcc (using /std:c++latest and -std=c++2a, respectively).
Is the requires simply discarded in scenarios like this? Should I not use it this way?
#include <type_traits>
template <
template < typename >
requires (false) // Should not this stop compilation?
typename Wrapper >
using Test = Wrapper < int >;
template < typename >
struct S
{
};
int main() {
Test < S > var;
return 0;
}
I think the compilers are not implementing this correctly and you are correct that it should fail to compile.
In [temp.names]/7 it says that a template-id formed from a template template parameter with constraints must satisfy these constraints if all template arguments are non-dependent.
You are giving Wrapper only one argument, namely int which is not dependent. Therefore the compiler should check whether Wrapper<int> satisfies the constraint requires(false) of Wrapper. This check should fail.
I am not completely sure that requires(false) specifically is not IFNDR, because there are some similar rules forbidding e.g. templates which can never be instantiated, but the compilers seem to behave the same way if a non-trivial constraint is used.
Clang complains that the requires clause is a syntax error in that position, but I don't see any reason for that.
MSVC actually handles for example the following variation using a type constraint instead of a requires-clause as expected:
template<
template<std::same_as<float> T>
typename Wrapper>
using Test = Wrapper<int>;
but does not reject as expected if a requires-clause is used:
template<
template<typename T>
requires std::same_as<float, T>
typename Wrapper>
using Test = Wrapper<int>;
Interestingly Clang crashes with an ICE on the former.

Is it possible to implement always_false in the C++ standard library?

There are cases where one uses an always_false helper to e.g. cause unconditional static_assert failure if instantiation of some template is attempted:
template <class... T> struct always_false : std::false_type {};
template<class T>
struct UsingThisShouldBeAnError {
static_assert(always_false<T>::value, "You should not use this!");
};
This helper is necessary because a template definition must (at least theoretically) have at least one set of template parameters for which a valid specialization can be produced in order for the program to be well-formed:
[temp.res]/8: 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
[...]
(Writing static_assert(false, "You should not use this!"); above would thus be ill-formed and a compiler could always fire the static assert, even without the template being instantiated, which is not the intention.)
Here is a quick sampling of questions involving this pattern (including further explanation):
Forbids functions with `static_assert`
Are static_asserts to be evaluated if a member template isn't instantiated?
Conditional compilation of templates
It might be useful to have always_false as a tool in the standard library so we don't have to constantly write it again. However, the answer to the following question makes me wonder whether this is even possible:
Dependent non-type parameter packs: what does the standard say?
There the argument is made (also with respect to [temp.res]/8) that std::enable_if_t<T> is always either void or not a type and that it is illegal for anyone to specialize it further. Therefore, a template that relies on the theoretical "specializability" of std::enable_if to avoid the [temp.res]/8 clause actually causes the program to be ill-formed, no diagnostic required.
Coming back to my question: If the standard provided always_false, it would have to forbid library users from specializing it as usual (for obvious reasons). But by the above reasoning, that would defeat the whole point of always_false (namely that it could theoretically be specialized to something other than std::false_type) - with respect to [temp.res]/8 it would be the same as using std::false_type directly.
Am I wrong in this reasoning? Or is it actually impossible for the standard library to provide always_false in a meaningful/useful way (without core language changes)?
In C++20, with lambda, you might do something like:
template <class... T> struct always_false : std::false_type {};
// To have true, but for a type that user code can't reuse as lambda types are unique.
template <> struct always_false<decltype([](){})> : std::true_type{};
After reflection, I think it is not possible: enclosing template might have other restrictions that the hypothetical type(s) for the specialization should fulfill:
With static_assert(is_enum_v<T> && always_false_v<T>), that type should be an enum.
And even more constrained, with static_assert(is_same_v<T, int> && always_false_v<T>), it is for int.
To paraphrase Jarod's idea, It could be something like
template <class... T> struct always_false : std::false_type {};
template <> struct always_false</* implementation defined */> : std::true_type{};
Where /* implementation defined */ can be filled by std::_ReservedIdentifer. User code can't access it, since the identifier is reserved to the library, but there exists a specialization that is true. That should avoid questions about the ODR and lambdas in specializations.
All such attempts result in a program that is ill-formed, no diagnostic required.
The clauses that block you from using static_assert(false) make your program ill-formed, no diagnostic required based on the actual possibility of making an actual instantiation, not based on if the compiler can work it out.
These tricks simply make it harder for the compiler to detect the fact your program is ill-formed. The diagnostic they issue is not required, and your ability to bypass the diagnostic being issued just means you made the compiler produce an ill-formed program for which the standard places no restrictions on its behavior.
(Writing static_assert(false, "You should not use this!"); above would thus be ill-formed and a compiler could always fire the static assert, even without the template being instantiated, which is not the intention.)
The exact same conclusion holds for your
template <class... T> struct always_false : std::false_type {};
template<class T>
struct UsingThisShouldBeAnError {
static_assert(always_false<T>::value, "You should not use this!");
};
I claim there is no valid instantiation of UsingThisShouldBeAnError in the above program.
http://eel.is/c++draft/temp.res#6.1
The program is ill-formed, no diagnostic required, if:
(6.1) no valid specialization can be generated for a template [...]"
No valid specialization can be generated for this template.
To avoid this trap, your program must have
template <> struct always_false<SomeListOfTypes> : std::true_type {};
and if an always_false is specified in the standard for which this cannot occur, using always_false from the standard doesn't help you at all.
because the standard requires that the specialization "can be generated".
If the only way to instantiate your template is within an ill-formed program, then that is a huge stretch on the word "can". So using reserved or magic types you aren't allowed to use within the true_type specialization is not really plausible.
Stepping back, the purpose of the "ill-formed, ndr" there is because the standard writers want to permit diagnostics of broken code, but don't want to mandate it. Detecting such broken code in general is a halt-hard problem, but simple cases can be detected. And optimizing around the assumption that the code isn't broken can be useful.
All attempts to inject static_assert(false, "message") are working around the intention that C++ code is intended to be valid.
We have a construct for a function for whom successful lookup is an error already in the language.
template<class T>
void should_never_be_found(tag<T>) = delete;
the hole, of course, is the lack of a diagnostic message here.
template<class T>
void should_never_be_found(tag<T>) = delete("Provide an custom ADL overload!");
also, the inability to =delete template specializations.
What you appear to want is:
template<class T>
struct UsingThisShouldBeAnError = delete("You should not use this");
which is direct, intentional, and consistent with how other =delete cases work.
The static_assert bypass requires magic and "tricking" the compiler to bypass parts of the standard explicitly written to prevent you from doing what you want to do.
This =delete("string") and =delete syntax on template classes is missing from the language.
just use option -fdelayed-template-parsing

Does -Werror interfere with template correctness and/or SFINAE?

I have the feeling that this is a dumb question and I guess that the answer is a simple "No", though I have no clue how to be certain about it other than asking for your help...
Does -Werror interfere with template correctness (not sure what is the right term, see example below) and/or SFINAE?
Consider this simple contrived example:
template <typename T>
void foo() {
int a;
}
int main() {
//int a; // error: unused variable 'a' [-Werror=unused-variable]
}
Uncommenting the line in main results in an error when compiled with -Werror. I know the compiler is supposed to generate an error for templates that are erroneous for any template parameter even if not instantiated. This is not the case here. I will only see the error (which is of course actually only a warning) here when I instantiate the template.
Why I ask this question: I am used to compile with -Werror always, hence my perception of what is a warning and what is an error is a bit blurry in some regards. Now for templates and especially SFINAE it does make a big difference if something is just a warning or really an error.
I know the compiler is supposed to generate an error for templates that are erroneous for any template parameter even if not instantiated.
That is not the case, though. If no instantiation can be generated for a template, then the program is ill-formed, no diagnostic required(1). So the program is ill-formed regardless of whether you get an error or it compiles "successfully."
Looking at it from the other perspective, a compiler must not allow a warning-turned-error to affect SFINAE, as that could change the semantics of a valid program and would thus make the compiler non-conforming. So if a compiler wants to diagnose a warning as an error, it must do this by stopping compilation and not by introducing a substitution failure.
In other words, -Werror can make the compiler reject a well-formed program (that is its intended purpose, after all), but it would be a compiler bug if it changed the semantics of one.
(1) Quoting C++17 (N4659), [temp.res] 17.6/8:
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
...
While it's largely a quality of implementation issue, -Werror can indeed (and does) interfere with SFINAE. Here is a more involved example to test it:
#include <type_traits>
template <typename T>
constexpr bool foo() {
if (false) {
T a;
}
return false;
}
template<typename T, typename = void> struct Check {};
template<typename T> struct Check<T, std::enable_if_t<foo<T>()>> {};
int main() {
Check<int> c;
}
The line T a; can trigger that warning (and error), even though the branch is dead (it's dead on purpose, so that foo is a constexpr function mostly regardless of T). Now, according to the standard itself, that is a well-formed program.
But because Clang and GCC cause an error there, and that error is in the non-immediate context of the Check specialization, we get a hard error. Even though according to the standard itself this should just fall back to the primary template due to substitution failure in the immediate context only.

What is the correct result of std::is_constructible<void()>::value?

I'm getting inconsistent results for std::is_constructible<void()>::value. My interpretation of the standard is that it should be false. However, Clang, with both libc++ and libstdc++*, gives true. GCC and MSVC both give false. Which result is correct?
Standardese
Here is the standardese, N4527 [meta.unary.prop]/7:
Given the following function declaration:
template <class T> add_rvalue_reference_t<T> create() noexcept;
the predicate condition for a template specialization
is_constructible<T, Args...> shall be satisfied if and only if the
following variable definition would be well-formed for some invented
variable t:
T t(create<Args>()...);
Note: This text changed slightly from C++11 (N3485), where create was not marked noexcept. However, the results of my tests did not change when accounting for this.
Test Case
Here is my minimal test case of both the type trait and the standardese definition:
#include <type_traits>
static_assert(std::is_constructible<void()>::value, "assertion fired");
template<typename T>
std::add_rvalue_reference_t<T> create() noexcept;
template<typename T, typename... Args>
void foo() {
T t(create<Args>()...);
}
int main() {
foo<void()>();
}
Results:
Clang (HEAD, libc++):
static assertion PASSED
foo<void()> did NOT compile
Clang (HEAD, libstdc++)*:
static assertion PASSED
foo<void()> did NOT compile
GCC (HEAD, libstdc++):
static assertion FAILED
foo<void()> did NOT compile
MSVC (version 19 via http://webcompiler.cloudapp.net/):
static assertion FAILED
foo<void()> did NOT compile (requires commenting out the static assertion)
*__GLIBCXX__ is not defined when Clang is used both with no -stdlib option and with -stdlib=libstdc++. I am unsure of whether libstdc++ is actually being used. If my interpretation of the standard is correct, then I am unsure of whether it is a bug with Clang or with libc++.
Keep reading. From the same paragraph:
Access checking is performed as if in a context unrelated to T and
any of the Args. Only the validity of the immediate context of the
variable initialization is considered. [ Note: The evaluation of the
initialization can result in side effects such as the instantiation of
class template specializations and function template specializations,
the generation of implicitly-defined functions, and so on. Such side
effects are not in the “immediate context” and can result in the
program being ill-formed. —end note ]
The assertion only fails when the template constructor is instantiated. However, as cleared up in the note, that assertion is not in the immediate context of the variable definition that is considered, and thus does not affect its "validity". So the compilers can count that definition as valid, even if actually attempting to construct a void() results in an ill-formed program.
Note that the compilers are also allowed to, instead of having is_constructible yield false, just reject the original program based on the assertion.