38

We consider the goal of creating two different types, using the exact same syntax. This can be easily done with lambdas:

auto x = []{};
auto y = []{};
static_assert(!std::is_same_v<decltype(x), decltype(y)>);

But instead of using lambdas, we are looking for another, more elegant syntax. Here are some tests. We start by defining some tools:

#include <iostream>
#include <type_traits>
#define macro object<decltype([]{})>
#define singleton object<decltype([]{})>

constexpr auto function() noexcept
{
    return []{};
}

template <class T = decltype([]{})>
constexpr auto defaulted(T arg = {}) noexcept
{
    return arg;
}

template <class T = decltype([]{})>
struct object
{
    constexpr object() noexcept {}
};

template <class T>
struct ctad
{
    template <class... Args>
    constexpr ctad(const Args&...) noexcept {}
};

template <class... Args>
ctad(const Args&...) -> ctad<decltype([]{})>;

and the following variables:

// Lambdas
constexpr auto x0 = []{};
constexpr auto y0 = []{};
constexpr bool ok0 = !std::is_same_v<decltype(x0), decltype(y0)>;

// Function
constexpr auto x1 = function();
constexpr auto y1 = function();
constexpr bool ok1 = !std::is_same_v<decltype(x1), decltype(y1)>;

// Defaulted
constexpr auto x2 = defaulted();
constexpr auto y2 = defaulted();
constexpr bool ok2 = !std::is_same_v<decltype(x2), decltype(y2)>;

// Object
constexpr auto x3 = object();
constexpr auto y3 = object();
constexpr bool ok3 = !std::is_same_v<decltype(x3), decltype(y3)>;

// Ctad
constexpr auto x4 = ctad();
constexpr auto y4 = ctad();
constexpr bool ok4 = !std::is_same_v<decltype(x4), decltype(y4)>;

// Macro
constexpr auto x5 = macro();
constexpr auto y5 = macro();
constexpr bool ok5 = !std::is_same_v<decltype(x5), decltype(y5)>;

// Singleton
constexpr singleton x6;
constexpr singleton y6;
constexpr bool ok6 = !std::is_same_v<decltype(x6), decltype(y6)>;

and the following test:

int main(int argc, char* argv[])
{
    // Assertions
    static_assert(ok0); // lambdas
    //static_assert(ok1); // function
    static_assert(ok2); // defaulted function
    static_assert(ok3); // defaulted class
    //static_assert(ok4); // CTAD
    static_assert(ok5); // macro
    static_assert(ok6); // singleton (macro also)

    // Display
    std::cout << ok1 << std::endl;
    std::cout << ok2 << std::endl;
    std::cout << ok3 << std::endl;
    std::cout << ok4 << std::endl;
    std::cout << ok5 << std::endl;
    std::cout << ok6 << std::endl;

    // Return
    return 0;
}

this is compiled with the current trunk version of GCC, with options -std=c++2a. See result here in compiler explorer.


The fact that ok0, ok5 and ok6 work are not really a surprise. However, the fact that ok2 and ok3 are true while ok4 is not is very surprising to me.

  • Could someone provide an explanation of the rules that make ok3 true but ok4 false?
  • Is it really how it should work, or this is a compiler bug concerning an experimental feature (lambdas in unevaluated contexts)? (reference to the standard or to C++ proposals are very welcomed)

Note: I really hope this is a feature and not a bug, but just because it makes some crazy ideas implementable

19
  • 21
    Tosses upvote and runs away Commented Apr 2, 2019 at 13:01
  • 6
    eel.is/c++draft/temp.arg#8 almost gives you an answer for ok2/ok3, for CTAD, there is a single definition of the constructor, so a single instantiation of the decltype([]{}). Commented Apr 2, 2019 at 13:12
  • 4
    hmmm... almost look like another way to do compile time stateful programming Commented Apr 2, 2019 at 13:26
  • 5
    I believe you're in violation of eel.is/c++draft/temp.res#8.5, but I'm not sure. Commented Apr 2, 2019 at 14:15
  • 3
    stackoverflow.com/q/54410522/9585016 Commented Apr 2, 2019 at 16:40

1 Answer 1

2

Could someone provide an explanation of the rules that make ok3 true but ok4 false?

ok3 is true because uses lambdas type as default type.

The type of a lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type,

Hence, default template type for object, template parameter type for macro and singltone always different after every instatiation. But, for function function call returned lambda is unique and its type is unique. Template function ctad has template only for parameters but return value is unique. If rewrite function as:

template <class... Args, class T =  decltype([]{})>
ctad(const Args&...) -> ctad<T>;

In this case return type will be defferent after every instantiation.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.