C++ Logo

std-proposals

Advanced search

Re: [std-proposals] attribute [[unevaluated]]

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 20 May 2025 23:17:41 +0100
A few changes need to be made to template metaprogramming in C++.

Consider the following program:

    #include <utility>
    #include <vector>

    template<typename T>
    decltype(auto) Func(void)
    {
        return std::declval<T&>().begin();
    }

    int main(void)
    {
        typedef decltype(Func< std::vector<int> >()) MyType;
    }

This fails to compile on GNU g++, LLVM clang, Intel ICX and the
Microsoft compiler. All four compilers complain that 'Func' uses
'declval' in an evaluated context. We can fix it as follows:

    #include <utility>
    #include <vector>

    template<typename T>
    decltype(auto) Func(void)
    {
        return ((T*)nullptr)->begin();
    }

    int main(void)
    {
        typedef decltype(Func< std::vector<int> >()) MyType;
    }

Or alternatively:

    #include <utility>
    #include <vector>

    template<typename T>
    auto Func(void) -> decltype(std::declval<T&>().begin());

    int main(void)
    {
        typedef decltype(Func< std::vector<int> >()) MyType;
    }

But let's say that we want our function to have a body, because maybe
the type deduction is a few dozen lines of code, like in the example I
gave here earlier today:

    https://lists.isocpp.org/std-proposals/2025/05/13764.php

Now let's take a second look at that original function:

    template<typename T>
    decltype(auto) Func(void)
    {
        return std::declval<T&>().begin();
    }

When the compiler encounters the body of this function, it sees that
'declval' is used in an evaluated context. I think the remedy here is
to tell the compiler that this function will never be executed, and so
the entire body of the function is to be considered to be an
unevaluated context. I propose we do this with the attribute of
"[[unevaluated]]".

    template<typename T>
    [[unevaluated]] decltype(auto) Func(void)
    {
        return std::declval<T&>().begin();
    }

So now the compiler won't complain about the use of 'declval'. This
might also result in faster compilation times as the compiler knows it
doesn't have to keep track of stack size, register values, nor write
any machine code.

The next thing to address is that 'declval' isn't good enough. It can
make an Lvalue or an Xvalue, but it can't make a PRvalue. Making a
change to 'declval' in C++29 would break existing code, so I propose
the introduction of a new standard library function:

    template<typename T>
    [[unevaluated]] consteval remove_cvref_t<T> prvalue(void) noexcept

This function will return a PRvalue for any type T (removing the
reference from T if necessary).

Here is a sample usage:

    template<typename T>
    [[unevaluated]] decltype(auto) SomeFunc(void)
    {
        return std::prvalue< decltype(typename T::value_type) >(); //
Can be both unmovable and uncopyable
    }

And also we should have another new function in the standard library
called "thin_air" as follows:

    template<typename T>
    [[unevaluated]] consteval decltype(auto) std::thin_air(void)

This function can yield an Lvalue, an Xvalue, or a PRvalue.

If you want an Lvalue, then you do: std::thin_air<MyClass& >();
If you want an Xvalue, then you do: std::thin_air<MyClass&&>();
If you want an PRvalue, then you do: std::thin_air<MyClass >();

The implementations of these two functions would be very simple.
Literally they would just be:

    namespace std {
        template<typename T>
        [[unevaluated]] consteval remove_cvref_t<T> prvalue(void)
noexcept; // no body needed

        template<typename T>
        [[unevaluated]] consteval T thin_air(void) noexcept; // no
body needed
    }

Programmers can continue to use "declval" if they want to (or maybe
deprecate it at some point).

Received on 2025-05-20 22:17:56