Article

What exactly does [[ "ill-formed" ]]] mean in this context?

Topic: TravelBy Rchard MathewPublished Recently added

Legacy signals

Legacy popularity: 207 legacy views

The phrase, "When checking the validity of a requirement, only the so-called immediate context of the expression is checked," refers to a key concept in C++ called immediate context during the compilation process. It means that during overload resolution or template instantiation, the compiler will check whether an expression can be compiled according to the context in which it is used. This context includes the immediate expressions, types, and template parameters involved in the call.

However, immediate context does not extend to a deep verification of types or instantiations that could happen later if the code were to be executed. This is a critical distinction because C++ allows some flexibility during compilation, especially when it comes to checking whether a given template call can potentially compile or not.

Let's dive deeper into how this idea applies in the context of std::get and overload resolution, with a particular focus on its behavior when provided with a template argument that might be "invalid" but does not result in an immediate error.

C++ Overload Resolution

C++ uses overload resolution to determine which function or template instantiation should be invoked when multiple candidates are available. The rules of overload resolution allow the compiler to choose a suitable function based on the function signature (return type, parameters, and sometimes, template arguments).

The std::get Function and Overload Resolution

The std::get function in C++ is part of the <tuple> library and is used to access elements from a tuple. The function comes in two major forms:

Non-template version that allows you to access elements using an index.

Template version that allows you to access an element by type.

The signature for std::get can look like this:

cpp

Copy code

template <std::size_t I, typename T> T& std::get(T& t); // Non-const version template <typename T, typename Tuple> T& std::get(Tuple& t);
The behavior of std::get with template arguments (especially the overload for accessing tuple elements by type) brings us to the core of the discussion.

Template Instantiations and Invalid Template Arguments

A template instantiation occurs when the compiler generates a function or a class template based on the provided template arguments. In the case of std::get, you can pass a specific index or a type as the template argument.

For instance:

cpp

Copy code

std::tuple<int, double, char> t; auto x = std::get<2>(t); // Access element at index 2
In this case, the template parameter 2 is a valid index, so overload resolution selects the appropriate version of std::get that accesses the third element of the tuple.

However, if you provide an "invalid" type or index (one that cannot match any element in the tuple), the compiler does not immediately check or enforce correctness of the expression—this only happens when the instantiation is needed. In other words, the immediate context of the expression during overload resolution is considered, but the verification of whether the instantiations that are necessary for the function to compile are well-formed, is deferred.

For example, if you try:

cpp

Copy code

auto x = std::get<char>(t); // Invalid, 'char' is not a valid index type.
The call compiles, but it results in the selection of the wrong overload—the overload for std::get<char>—because there is no early rejection of invalid template arguments.

Delayed Validation: How Invalid Template Arguments Are Handled

The key here is that C++ does not immediately reject invalid template arguments during overload resolution. In this case, C++ allows the overload for std::get<char> to be selected, assuming that char is a valid type for tuple indexing. This results in what might be perceived as a "quirk" in the way C++ overload resolution works. However, it's important to note that while the overload resolution succeeds in selecting an incorrect template, the instantiation itself will fail when the code tries to access an element of the tuple based on the invalid template argument.

The Role of cppreference and Function Specification

The documentation for std::get (available on cppreference) specifies that the call to std::get will be ill-formed if the template argument is invalid. This is a late-stage check, meaning that the invalid template argument is not rejected outright during overload resolution but only once the function is fully instantiated.

The key line in cppreference about the ill-formed state is:

"The call shall be ill-formed if the template argument is invalid."

This indicates that while overload resolution can select an incorrect function template based on the immediate context (such as std::get<char>), the template instantiation will fail when evaluated, because the tuple does not contain a type char at the specified index.

FAQ

1. Why is the immediate context important during overload resolution?

The immediate context ensures that the compiler only checks the most relevant aspects of the expression when resolving overloads. The compiler checks the types and arguments directly involved in the expression but does not require a deeper analysis of the instantiations that will occur later. This approach allows for more flexible and efficient compile-time behavior, especially in complex templated code.

2. Why does overload resolution succeed even if the template argument is invalid?

Overload resolution works by considering only the types in the immediate context. If a function template exists that matches the argument types (even if those arguments are not valid for later use), the compiler will choose that template. However, the actual instantiation will fail later if the arguments don't make sense (i.e., when the code is fully evaluated or instantiated).

3. What happens when I pass an invalid template argument to std::get?

If you pass an invalid argument (such as an incorrect index or type) to std::get, overload resolution may select a corresponding overload. However, when the code is fully evaluated, you will get a compilation error because the template argument does not match any valid element in the tuple.

For example:

cpp

Copy code

std::tuple<int, double, char> t; auto x = std::get<char>(t); // Will fail at instantiation, as `char` is not a valid index type.

4. What exactly does "ill-formed" mean in this context?

"Ill-formed" means that the code, once fully instantiated, is not valid according to the C++ language rules. In the case of std::get, this occurs when the provided template argument cannot correspond to a valid element in the tuple. This error will only surface when the function is instantiated.

5. Does this behavior apply to other template functions in C++?

Yes, this behavior of considering only the immediate context during overload resolution applies more broadly in C++. Many template functions or function templates may seem valid during overload resolution but fail when instantiating with invalid or mismatched arguments. This phenomenon is not unique to std::get but is part of C++'s general overload resolution and template instantiation mechanisms.

6. Can this issue be avoided by using constraints or SFINAE?

Yes, constraints such as std::enable_if or C++20's concepts can be used to enforce stricter checks on template arguments, ensuring that only valid types or values are passed to the function. This would allow you to reject incorrect arguments at the time of overload resolution, avoiding the delayed ill-formed error.

For example, you could use:

cpp

Copy code

template <typename T> std::enable_if_t<std::is_integral_v<T>> foo(T t) { ... }

7. Is there a way to preemptively reject invalid template arguments in std::get?

In some cases, concepts or static_assert statements might be used to check the validity of template arguments before the function is called. For instance, you could define a concept to ensure that the template argument is a valid type for indexing a tuple, rejecting the call at compile time if the argument does not satisfy the concept.

8. Does this issue affect performance?

The issue itself—where an invalid template argument is selected during overload resolution but only fails during instantiation—does not affect performance directly. However, failing to catch errors earlier in the compilation process can lead to confusion and longer compile times as the compiler tries to instantiate and evaluate the code.

9. Are there any best practices to avoid similar issues?

To avoid similar issues:

Use constraints (such as SFINAE, enable_if, or concepts) to ensure that only valid template arguments are passed.

Always use explicit, clear template arguments and avoid relying on compiler-generated error messages for template instantiation errors.

10. Could this behavior lead to bugs in large codebases?

Yes, in larger codebases, relying on overload resolution to "choose the right overload" could potentially lead to subtle bugs. These bugs may arise when incorrect template arguments are passed to functions like std::get, only for the error to be discovered later during instantiation. Therefore, careful validation of template arguments (e.g., with static assertions or concepts) is recommended.

Conclusion

In conclusion, the behavior where overload resolution succeeds even with an invalid template argument, but fails at a later stage, illustrates a key aspect of C++'s template system. The immediate context is used during overload resolution to select candidate functions, but the final check of validity occurs during instantiation. While this provides flexibility, it also means that certain types of errors are deferred until later in the compilation process.

Article author

About the Author

Rchard Mathew is a passionate writer, blogger, and editor with 36+ years of experience in writing. He can usually be found reading a book, and that book will more likely than not be non-fictional.

Further reading

Further Reading

4 total

Article

Pattaya, Thailand is known for its sapphire beaches, crazy nightlife and vibrant culture. While it might take a week or longer to soak up the Thai atmosphere, you can make a short trip to enjoy the coastal city of Thailand. This blog will share how you can make it “around Pattaya all-inclusive holidays in 2 days”. So, let’s begin the new adventure. Explore Pattaya in 2 DAYS Here are some of the places that you visit during your short trip to Pattaya. Begin at Central Pa

December 13, 2024

Article

Umrah is no small blessing for pilgrims, as it cleanses their souls of past sins and renews faith in Allah. While it is not compulsory to perform, the holy cities of Mecca and Medina are full of pilgrims for most of the year. This is why different agencies and sometimes airlines offer special travel packages designed specifically for Umrah. These deals cover everything from flights to hotel stays. For those who are planning their first pilgrimage, it can be a bit difficult an

September 23, 2024

Article

Are you dreaming of luxury holidays but need help figuring out where to start? Look no further! This blog will take you through finding the perfect yacht for your luxury holiday , from research to booking. Along the way, you'll be inspired by popular luxury holiday itineraries and find the ideal yacht. So whether you're looking for a relaxing getaway or a thrilling sailing adventure, this blog has everything you need to make it happen! Access the world's superyachts Charterin

March 5, 2024

Article

A yacht is a kind of boat that offers different services to make your vacations enjoyable. Yacht let you sail across different sea areas and spend time while staying in comfort. The yacht is a fully furnished cruise that contains different departments where you can spend time. It lets you sit in the finest comfort and takes you through different locations. A yacht can be the best way to spend holidays because of different services. There are different areas where you can crui

February 12, 2024