Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Invalid explicitly-specified argument in clang but successful compilation in gcc — who's wrong?

The following code compiles without problems in g++:

#include <iostream>
#include <string>
#include <tuple>

template<typename T>
void test(const T& value)
{
    std::tuple<int, double> x;
    std::cout << std::get<value>(x);
}

int main() {
    test(std::integral_constant<std::size_t,1>());
}

I used this command:

g++ test.cpp -o test -std=c++14 -pedantic -Wall -Wextra

But when I switch g++ to clang++ (with g++ 5.1.0 and clang++ 3.6.0), I get the following errors:

test.cpp:9:18: error: no matching function for call to 'get'
    std::cout << std::get<value>(x);
                 ^~~~~~~~~~~~~~~
test.cpp:13:5: note: in instantiation of function template specialization 'test<std::integral_constant<unsigned long, 1> >' requested here
    test(std::integral_constant<std::size_t,1>());
         ^~~~~~~~~~~~~~~
<skipped>

/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/tuple:867:5: note: candidate template ignored: invalid explicitly-specified argument for template parameter '_Tp'
    get(tuple<_Types...>& __t) noexcept
    ^

And similar note: entries for other overloads of std::get.

But I'm passing std::integral_constant to test(), which is a constant-expression, why would it be an "invalid explicitly-specified argument" for the template parameter? Is it a clang bug or am I doing something wrong here?

I've noticed that if I change parameter for test() from const T& to const T, then clang compiles successfully. Do I somehow lose constexpr quality of integral_constant by passing it by reference?

like image 566
Ruslan Avatar asked Nov 23 '15 13:11

Ruslan


1 Answers

As there is no answer for a week, I will post my vision. I am far from being an expert at language-laywering, actually I would consider myself a complete novice, but still. The following is based on my reading of standard, as well on my recent question.

So, first of all let's rewrite the code the following way:

struct A {
    constexpr operator int() const { return 42; }
};

template <int>
void foo() {}

void test(const A& value) {
    foo<value>();
}

int main() {
    A a{};
    test(a);
}

It exhibits the same behavior (builds with gcc and fails with similar error with clang), but:

  • is free from template type deduction at test(), to make sure the problem has nothing to do with type deduction,
  • uses 'mocks' instead of std members to make sure this is not a problem with their implementation,
  • and has an explicit variable a, not a temporary, to be explained later.

What does happen here? I will quote N4296.

We have a template foo with a non-type parameter.

[14.3.2(temp.arg.nontype)]/1:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter.

So template argument, i.e. value, should be a converted constant expression of type int.

[5.20(expr.const)]/4:

A converted constant expression of type T is an expression, implicitly converted to type T, where the converted expression is a constant expression and the implicit conversion sequence contains only

  • user-defined conversions,
  • ... (irrelevant bullets dropped)

and where the reference binding (if any) binds directly.

Our expression (value) can be implicitly converted to type int, and the conversion sequence contains only user-defined conversions. So the two questions remain: whether "the converted expression is a constant expression" and whether "the reference binding (if any) binds directly".

For the first question, the phrase "the converted expression", I think, means the expression as already converted to int, that is something like static_cast<int>(value), not the original expression (value). For that,

[5.20(expr.const)]/2:

A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine (1.9), would evaluate one of the following expressions:

  • ... (a long list omitted)

Evaluation of our expression, static_cast<int>(value), leads only to evaluation of A::operator int(), which is constexpr, and thus is explicitly allowed. No members of A (if there were any) are evaluated, neither anything else is evaluated.

Therefore, static_cast<int>(value) is a constant expression.

For the second question, about reference binding, it is not clear for me to which process this refers at all. However, anyway we have only one reference in our code (const A& value), and it binds directly to the variable a of main (and this is the reason why I introduced a).

Indeed, direct binding is defined at the end of [8.5.3(dcl.init.ref)]/5:

In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.

This "last" case seem to refer to 5.2, and non-direct binding means initialization from a temporary (like const int& i = 42;), not our case when we have a non-temporary a.

UPD: I asked a separate question to check whether my understanding of the standard, presented above, is correct.


So the bottomline is that the code should be valid, and clang is wrong. I suggest you filing a bug to clang bug tracker, with a reference to this question. Or if for whatever reason you will not file a bug, let me know, I will file it.

UPD: filed a bug report

like image 55
Petr Avatar answered Nov 15 '22 17:11

Petr