[temp.constr.decl] says that we can constrain a template or a function with a constraint expression.
Declarators [dcl.decl] tells us that, for functions, we may add an optional trailing requires clause to constrain it, and the standard draft n4820 even gives these (seemingly pointless) examples:
void f1(int a) requires true; auto f2(int a) -> bool requires true;
I understand that constraining a template or a concept is useful, but I fail to see how these constraints are useful for non-templated functions. What's the point of constraining a non-templated function?
Constraints are applied to type parameters to place limitations on the types that can be used as arguments for a generic type or method. Class and interface constraints specify that the argument types must be or inherit from a specified class or implement a specified interface.
Character constants are one or more members of the “source character set,” the character set in which a program is written, surrounded by single quotation marks ('). They are used to represent characters in the “execution character set,” the character set on the machine where the program executes.
What is the difference between normal function and template function? Explanation: As a template feature allows you to write generic programs. therefore a template function works with any type of data whereas normal function works with the specific types mentioned while writing a program.
To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>( float original ); Template arguments may be omitted when the compiler can infer them.
Just as a concept consider the following example
#include <iostream> void f( long x ) requires ( sizeof( long ) == sizeof( int ) ) { std::cout << "Bye " << x << '\n'; } void f( long long x ) requires ( sizeof( long ) == sizeof( long long ) ) { std::cout << "Hello " << x << '\n'; } int main() { f( 0l ); }
If sizeof( long ) == sizeof( long long )
then the program output will be
Hello 0
Otherwise
Bye 0
For example you can use such an approach in a function that calculates the factorial to restrict the number of a loop iterations or to throw an exception.
Here is a demonstrative program.
#include <iostream> #include <stdexcept> unsigned long factorial( unsigned long n ) noexcept( false ) requires ( sizeof( unsigned long ) == sizeof( unsigned int ) ) { const unsigned long MAX_STEPS = 12; if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." ); unsigned long f = 1; for ( unsigned long i = 1; i < n; i++ ) f *= ( i + 1 ); return f; } unsigned long long factorial( unsigned long long n ) noexcept( false ) requires ( sizeof( unsigned long ) == sizeof( unsigned long long ) ) { const unsigned long long MAX_STEPS = 20; if ( MAX_STEPS < n ) throw std::out_of_range( "Too big value." ); unsigned long f = 1; for ( unsigned long long i = 1; i < n; i++ ) f *= ( i + 1 ); return f; } int main() { unsigned long n = 20; try { std::cout << factorial( n ) << '\n'; } catch ( const std::out_of_range &ex ) { std::cout << ex.what() << '\n'; } }
Its output might be either
2432902008176640000
or
Too big value.
One of the main points of constraining non-template functions is to be able to write constraints to non-template members of template classes. For example, you might have some type like this:
template<typename T> class value { public: value(const T& t); value(T&& t); private: T t_; };
Now, you want value
to be copyable/moveable from T
. But really, you want it to be copyable/moveable from T
only as far as T
itself is copyable/moveable. So, how do you do it?
Pre-constraints, you would need to write a bunch of meta-programming hackery. Maybe you make these constructors templates, which require that the given type U
is the same as T
, in addition to the copy/move requirement. Or you might have to write a base class that you inherit from, which has different specializations based on the copy/moveability of T
.
Post-constraints, you do this:
template<typename T> class value { public: value(const T& t) requires is_copy_constructible_v<T> : t_(t) {} value(T&& t) requires is_move_constructible_v<T> : t_(std::move(t)) {} private: T t_; };
No hackery. No applying templates to functions that don't need to be templates. It just works, and it's easy for the user to understand what is going on.
This is especially important for functions which cannot be templates. In order for a constructor to be considered a copy or move constructor, it cannot be a template. Same goes for copy/move assignment operators. But such things can have constraints.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With