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
butok4
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
ctad
has no default argument. The single Landa definition is same. I'd be curious whether it is the same when the template arguments differ. – Rabassaok2
/ok3
, for CTAD, there is a single definition of the constructor, so a single instantiation of thedecltype([]{})
. – Testifydefaulted<decltype([]{})>()
twice. And just as we get two different types for the two[]{}
withx0
andy0
, we get two of these forx2
/y2
... – Elinaelinorconstexpr
, then I realized, and now I wish I hadn't so I wouldn't have to think about the inevitable horrors that are sure to follow. I can feel the agony of the standard writers from over here... – Anniceannieconstexpr
rabbit hole goes... – Primrosafoo<>
is equivalent tofoo<decltype([]{})>
. I'll admit I have trouble rationalizing why CTAD behaves differently, though. wandbox.org/permlink/LMD2QRA6DPVX3ka3 – Conoiddecltype([]{})
is not a dependent name and thus evaluated in the deduction guide definition already? – Anniceannie