NoSenseEtAl
NoSenseEtAl

Reputation: 30028

C++ concept member check type ambiquity with reference

I am learning C++ concepts, and I have an obnoxious problem:

I do not know how to differentiate between member variable being variable of type int and member variable being a int&.

Reason for this is that check I am using is using instance.member syntax and in C++ that returns a reference.

Full example:

#include <iostream>
#include <concepts>

template<typename T>
void print(T t) {
    std::cout << "generic" << std::endl;
}

template<typename T>
requires requires(T t){
    {t.val} -> std::same_as<int&>;
}
void print(T t) {
    std::cout << "special" << std::endl;
}

struct S1{
    int bla;
};

struct S2{
   int val = 47;
};
int x = 47;
struct S3{
    int& val=x;
};

int main()
{
    print(4.7);
    print(S1{});
    print(S2{});
    print(S3{});
}

I wish print(S3{}) would be handled by generic case, not the special one. Note that changing my requires stuff to:

 {t.val} -> std::same_as<int>;

makes S2 not match the template so that does not work(like I said I think that member access in C++ returns a reference).

Is there a way to fix this?

Upvotes: 1

Views: 1915

Answers (2)

HolyBlackCat
HolyBlackCat

Reputation: 96166

The solution is:

template <typename T>
requires requires(T t)
{
    requires std::is_same_v<decltype(t.val), int>; // or `int &` for references
}
void print(T t)

Clang has a bug that causes {T::val} -> std::same_as<int> to work as well, even though the type of the lhs is said to be determined as if by decltype((...)), which should return int & here.


Note that "member access in C++ returns a reference" is false. It can't be the case because expressions can't have reference types. When you write a function with a reference return type, calling it produces an lvalue of a non-reference type.

decltype will add reference-ness to the expression types depending on value category (& for lvalues, && for xvalues, nothing for prvalues). That's why people often think that expressions can have reference types.

It also has a special rule for variables (as opposed to general expressions), which causes it to return the variable type as written, disregarding the expression type & value category. And apparently t.val counts as a variable for this purpose.

Upvotes: 1

Barry
Barry

Reputation: 302942

The problem here is that expression concept checks use decltype((e)) in the check rather than decltype(e) (the extra parentheses matter).

Because t.val is an lvalue of type int (expressions never have reference type), decltype((t.val)) is int& regardless, as you've discovered.

Instead, you need to explicitly use the single-paren syntax:

template <typename T>
requires requires (T t) {
    requires std::same_as<decltype(t.val), int&>;
}
void print(T t) {
    std::cout << "special" << std::endl;
}

Or

template <typename T>
requires std::same_as<decltype(T::val), int&>

Upvotes: 4

Related Questions