Behavior of If constexpr in C++ - c++

Even having a lot a questions about this topic, I am having more and more problems. I think the problem is in understanding the meaning of certain words. All quotes below are from Cppreference
What does 'discarded' mean in the text below? I understand 'discarded' as something never compiled/touched, something that, whatever it is (like random characters that would be errors), it won't interfere in the rest of the program.
In a constexpr if statement, the value of condition must be a
contextually converted constant expression of type bool (until
C++23)an expression contextually converted to bool, where the
conversion is a constant expression (since C++23). If the value is
true, then statement-false is discarded (if present), otherwise,
statement-true is discarded.
What 'instantiaded' mean ?
If a constexpr if statement appears inside a templated entity, and if
condition is not value-dependent after instantiation, the discarded
statement is not instantiated when the enclosing template is
instantiated .
What 'checked' mean? I understand that 'checked' means the code was totally compiled and verified for any possible error at that time.
Outside a template, a discarded statement is fully checked. if
constexpr is not a substitute for the #if preprocessing directive:

Consider this example:
#include <iostream>
#include <string>
template <typename T>
void foo() {
T t;
if constexpr (std::is_same_v<T,std::string>){
std::cout << t.find("asd");
} else {
t = 0;
std::cout << t;
}
}
int main () {
foo<int>(); // (2)
}
When T is a type that does not have a find method, then std::cout << t.find("asd") is an error. Nevertheless, the template is ok.
What 'instantiaded' mean ?
The template is instantiated in (2). foo is just a template, instantiating it results in a function foo<int> that you can call.
What does 'discarded' mean in the text below?
The true-branch is discarded when foo<int> is instantiated (because the condition is false). Hence, even though int has no find method, the code compiles without error.
Now consider this similar, but very different example:
#include <iostream>
int main () {
int x = 0;
if constexpr (true) {
std::cout << x;
} else {
x.find("asd");
}
}
What 'checked' mean?
The text is a little contrived, it says that in the above example the false branch is discarded, but nevertheless it is checked, because it is outside of a template. It is just the english term: "checked" means the compiler checks if the code is correct. int has no method find, hence the above results in an error:
<source>:8:15: error: request for member 'find' in 'x', which is of non-class type 'int'
8 | x.find("asd");
| ^~~~
Even though this statement is never executed, it must be valid code.

Related

Why does a non-constexpr std::integral_constant work as a template argument?

My question is why the following code is valid C++:
#include <iostream>
#include <tuple>
#include <type_traits>
std::tuple<const char *, const char *> tuple("Hello", "world");
std::integral_constant<std::size_t, 0> zero;
std::integral_constant<std::size_t, 1> one;
template<typename T> const char *
lookup(T n)
{
// I would expect to have to write this:
// return std::get<decltype(n)::value>(tuple);
// But actually this works:
return std::get<n>(tuple);
}
int
main()
{
std::cout << lookup(zero) << " " << lookup(one) << std::endl;
}
Of course, I'm happy to be able to program this way. Moreover, I understand that std::integral_constant has a constexpr conversion operator. However, the parameter n to lookup is not constexpr, so I'm confused as to how a non-static method on a non-constexpr object (even if the method itself is constexpr) can possibly return a compile-time constant.
Of course, we happen to know in this case that the body of the conversion operator doesn't look at the runtime value, but nothing in the type signature guarantees that. For example, the following type obviously doesn't work, even though it, too, has a constexpr conversion operator:
struct bad_const {
const std::size_t value;
constexpr bad_const(std::size_t v) : value(v) {}
constexpr operator std::size_t() const noexcept { return value; }
};
bad_const badone(1);
Is there some extra property of methods that they are considered differently if they ignore the implicit this argument?
Of course, we happen to know in this case that the body of the conversion operator doesn't look at the runtime value, but nothing in the type signature guarantees that.
Correct. What you are missing is the fact that this doesn't matter.
When a function is called during constant expression evaluation, the compiler checks whether the function is constexpr. If it's not constexpr, then the enclosing evaluation fails to yield a constant expression.
But if it is constexpr, then the compiler executes its body at compile time and checks whether anything in its body disqualifies the enclosing evaluation from being a constant expression.
So in the case of the conversion operator of std::integral_constant, the compiler, executing its body at compile time, sees that it simply returns the value of the template parameter, which is fine.
If the compiler were evaluating a constexpr member function of some other type, which reads a non-const non-static data member of the object (and the object is not constexpr), then the compiler, at that point, would determine that the enclosing evaluation is not a constant expression.
It's a bit upsetting that when you see a function that has been declared constexpr, this tells you nothing about which evaluations of that function yield constant expressions, but that's the way it is.
In std::get<n>() the n needs to be a size_t but you pass a std::integral_constant<...>. So the compiler checks if there is an implicit conversion from one to the other, which attempts to call std::integral_constant<...>::operator size_t(). As it happens that is exactly the operator your std::integral_constant<std::size_t, 0> zero; has. So everything works out fine type wise.
But you asked why this is a compile-time constant: The operator size_t() returns the static constexpr T value = v;. So it doesn't matter if the integral_constant itself is const or not. It has no influence on the returned constant.

How to convert a variable from an int into a template parameter only if it is convertable in c++

I am trying to write a function that takes a variable of arbitrary type and sets it to an int value only if the type of the given variable can be converted to an int. My simplified code:
template <typename T>
void getInt(T& param)
{
int int_value = calculate_int_value();
if(std::is_convertible_v<int, T>){
param = static_cast<T>(int_value);
}
}
However, when I try to compile it, I get errors such as
error: no matching function for call to 'std::__cxx11::basic_string<char>::basic_string(int&)'
error: invalid conversion from 'int' to 'const char*'
Which makes me think I can't just convert an int value of arbitrary type inside an if statement, which proves it convertibility. How do I achieve my goal? Did I miss something?
std::is_convertible_v was added in C++17, if your code uses it then this means that your compiler support C++17, which also has if constexpr:
if constexpr(std::is_convertible_v<int, T>){
param = static_cast<T>(int_value);
}
In a regular if, even if it always evaluates to false, whatever's in the if statement must still be valid C++. Even if the body of a regular if statement is never executed it still must compile as valid C++. Attempting to convert something to int when it is proven not to be convertible to an int will, of course, not work.
if constexpr makes it possible to do otherwise (as long as if itself is a compile-time constant expression). The body of the if statement must still be syntactically valid C++, but only syntactically valid.

Should a template be instantiated in short-circuit evaluation? [duplicate]

Program A produces a compilation error (as expected) since isFinite is called with a non-integral type.
Program A
#include <iostream>
class Foo {};
template<typename T>
bool isFinite(const T& t)
{
static_assert(std::is_integral<T>::value, "Called isFinite with a non-integral type");
return false;
}
int main()
{
Foo f;
std::cout << "Foo is finite? " << ((isFinite(f)) ? "yes" : "no") << "\n";
return 0;
}
However, a slight modification (see Program B) allows the program to compile (Visual Studio 2013) and produce the following output.
Program B Visual Studio 2013 Ouput
Foo is finite? yes
Program B
#include <iostream>
class Foo {};
template<typename T>
bool isFinite(const T& t)
{
static_assert(std::is_integral<T>::value, "Called isFinite with a non-integral type");
return false;
}
int main()
{
Foo f;
std::cout << "Foo is finite? " << ((true || isFinite(f)) ? "yes" : "no") << "\n";
return 0;
}
It appears that Program B is short circuiting on the logical OR operation and not attempting to compile the rest of the expression. However, this application does not compile using g++ 4.8.3 (g++ -std=c++11 -o main main.cpp). I get the following output.
main.cpp: In instantiation of 'bool isFinite(const T&) [with T = Foo]':
main.cpp:15:56: required from here
main.cpp:8:2: error: static assertion failed: Called isFinite with a non-integral type
static_assert(std::is_integral<T>::value, "Called isFinite with a non-integral type");
^
My intuition leads me to believe that the compilation failure is the correct behavior but it is curious that Visual Studio 2013 compiles successfully. My intuition is based on the fact that it is expected that the following code cannot be compiled.
#include <iostream>
struct Foo
{
void doOperation1() {}
void doOperation2() {}
};
struct Bar
{
void doOperationA() {}
void doOperation2() {}
};
template<typename T>
void performOperation(T& t, bool value)
{
if (value)
{
t.doOperation1();
}
else
{
t.doOperation2();
}
}
int main()
{
Foo f;
performOperation(f, true);
performOperation(f, false);
Bar b;
performOperation(b, false); // Fails to compile (as expected)
return 0;
}
Restated Question
Are the logical operators supposed to adhere to short circuit evaluation rules at compile time (i.e., what is the expected compilation behavior of Program B)?
Short circuit is not supposed to compile true || (whatever_ill_formed). isFinite<Foo> is instantiated as part of expression and during instantiation it should be compiled and during compilation it should static assert. After that the compiler may never evaluate isFinite<Foo>(f) because of short circuit but static assert is not supposed to happen during it.
It is unclear why Visual Studio 2013 compiles Program B. Standard only allows bypassing syntax checking of templates when template is never instantiated. Even then the code is still ill formed only diagnostics are not required. Behind the defect is perhaps the same internal issue in Visual C++ that does not let Microsoft to implement constexpr.
Edit I add some language lawyer texts from standard per #zneak request
3.2/3
A function whose name appears as a potentially-evaluated expression is
odr-used if it is the unique lookup result or the selected member of a
set of overloaded functions (3.4, 13.3, 13.4), unless it is a pure
virtual function and its name is not explicitly qualified. [Note: This
covers calls to named functions (5.2.2), operator overloading (Clause
13), user-defined conversions (12.3.2), allocation function for
placement new (5.3.4), as well as non-default initialization (8.5). A
constructor selected to copy or move an object of class type is
odr-used even if the call is actually elided by the implementation
(12.8). —end note]
5.13/1
The || operator groups left-to-right. The operands are both
contextually converted to bool (Clause 4). It returns true if either
of its operands is true, and false otherwise. Unlike |, || guarantees
left-to-right evaluation; moreover, the second operand is not
evaluated if the first operand evaluates to true.
7.1/4
In a static_assert-declaration the constant-expression shall be a
constant expression (5.19) that can be contextually converted to bool
(Clause 4). If the value of the expression when so converted is true,
the declaration has no effect. Otherwise, the program is ill-formed,
and the resulting diagnostic message (1.4) shall include the text of
the string-literal, except that characters not in the basic source
character set (2.3) are not required to appear in the diagnostic
message.
14.7.1/3
Unless a function template specialization has been explicitly
instantiated or explicitly specialized, the function template
specialization is implicitly instantiated when the specialization is
referenced in a context that requires a function definition to exist.

constexpr array reference parameter

Can someone please explain why the marked line below compiles fine:
template<typename T, int N>
constexpr
int get_size(T (&)[N])
{
return N;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Intuitively to me, get_size(xs) isn't a constant expression because xs itself isn't so I don't understand why it works.
After the template function is instantiated your program becomes equivalent to the following:
constexpr
int get_size(int (&)[10])
{
return 10;
}
int main()
{
int xs[10];
constexpr int y = get_size(xs); // HERE.
static_assert(10 == y, "wrong size");
}
Then after function invocation substitution it becomes equivalent to the following:
int main()
{
int xs[10];
constexpr int y = 10; // HERE.
static_assert(10 == y, "wrong size");
}
Function invocation substitution is described under 7.1.5 [dcl.constexpr]/5. Essentially parameters are replaces as if copy-initialized and then subsituted for occurences in the return expression. The return expression then likewise as-if copy-initializes the return value. The resulting expression then becomes the expression that is subsituted for the function call. Only after this is the expression considered if it satisfies the constraints on constant expressions placed by the context. (Note, a quality compiler can of course determine that the constexpr function can never be used successfully after any such operation, and can fail after encounting the function definition, but it doesn't have to)
Also note, just to confuse you this concept is removed in C++14 and replaced with a different concept for how constexpr functions are evaluated. Among other things you will be able to use if statements, for statements and local variables of literal type within constexpr functions.
Your question and the comment:
I guess I'm confused why an automatic variable whose address isn't known can be passed by reference to a function used in a constant expression
When the compiler sees get_size(xs), it has already parsed the previous line which is int xs[10]; and thus knows the type and size of xs. There is nothing going to change at runtime, as far the type and the size is concerned — and these two are the information required by the compile in order to instantiate the function template, so it doesn't face any problem instantiating the function template, which in this case behaves as constexpr because everything is known at compile-time, which is why the static_assert doesn't fail.

What does -> after a function prototype mean?

What is happening in this code? Is so confusing.
#include <utility>
struct check
{
template <typename T>
auto foo() -> decltype(std::declval<T>().value, void())
{
static_assert(T{}.value == 10, "Incorrect value");
}
} var;
int main()
{
struct apple
{
int value{10};
};
var.foo<apple>();
}
Specifically the part where it has -> and everything after that.
Let's go through bit by bit.
auto foo() -> decltype(std::declval<T>().value, void())
This is a trailing return type. It's allowed to use parameters, but here that isn't necessary. I'd guess it's written like that to be clearer. decltype finds the type of the expression inside, but that expression is not actually evaluated. std::declval is used for creating an instance of the type passed to it. The comma operator is used here to make the overall return type void, since the comma operator evaluates the left side, throws it away, evaluates the right side, and returns that.
The first part creates a sort of SFINAE (though I've never seen it used like this). For example, if you had an overload of foo that did the same with value2 instead of value, there would be no ambiguity of which to call. See here for what I mean. Compare it to this one, which just has a return type of void and causes errors.
static_assert(T{}.value == 10, "Incorrect value");
This line makes sure a value-initialized instance of T has its value member have a value of 10. If it doesn't, a compiler error with that text is generated.
} var;
This is just a global object of that class to use.
struct apple
{
int value{10};
};
This is a sample class to test it with. It has a value member and that member is 10 in a value-initialized instance (default-initialized as well).
var.foo<apple>();
This just calls the function.