llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.09k stars 11.99k forks source link

compile error related to `static_assert` and `boost::hana` #32665

Open llvmbot opened 7 years ago

llvmbot commented 7 years ago
Bugzilla Link 33318
Version 4.0
OS Linux
Reporter LLVM Bugzilla Contributor
CC @zygoloid

Extended Description

I originally posted this as a question on Stack Overflow and it was suggested to me to file this as a bug here (https://stackoverflow.com/questions/44355720).

The following program compiles successfully using Clang versions 3.8.0-2ubuntu4, 4.0.1-svn304242-1~exp1 and 5.0.0-svn304672-1~exp1 using -std=c++14. The Boost library version is 1.61.0.

========== BEGIN SOURCE 1 ==========

#include <boost/hana.hpp>

namespace hana = boost::hana;

int main() {
    constexpr auto indices = hana::range<unsigned, 0, 3>();
    hana::for_each(indices, [&](auto i) {
        hana::for_each(indices, [&](auto j) {
            constexpr bool test = (i == (j == i ? j : i));
            static_assert(test, "error");
        });
    });
}

========== END SOURCE 1 ==========

The test is quite non-sensical but that is not the point. Consider now an alternative version where the test is put inside the static_assert:

========== BEGIN SOURCE 2 ==========

#include <boost/hana.hpp>

namespace hana = boost::hana;

int main() {
    constexpr auto indices = hana::range<unsigned, 0, 3>();
    hana::for_each(indices, [&](auto i) {
        hana::for_each(indices, [&](auto j) {
            static_assert((i == (j == i ? j : i)), "error");
        });
    });
}

========== END SOURCE 2 ==========

I get a bunch of compile errors saying:

test.cpp:7:38: note: 'i' declared here
    hana::for_each(indices, [&](auto i) {
                                     ^
test.cpp:9:39: error: reference to local variable 'i' declared in enclosing lambda expression
            static_assert((i == (j == i ? j : i)), "error");
                                      ^

The curious thing is, that when accessing i before the static_assert, everything compiles again:

========== BEGIN SOURCE 3 ==========

#include <boost/hana.hpp>

namespace hana = boost::hana;

int main() {
    constexpr auto indices = hana::range<unsigned, 0, 3>();
    hana::for_each(indices, [&](auto i) {
        hana::for_each(indices, [&](auto j) {
            constexpr auto a = i;
            static_assert((i == (j == i ? j : i)), "error");
        });
    });
}

========== END SOURCE 3 ==========

Note in particular that the added statement is unrelated to the static_assert.

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 7 years ago

Reduced testcase:

void f(int i) {
  [&](auto) { static_assert(&i != nullptr); }(0);
}

It appears that the problem is that we do not capture i when processing the generic lambda. Then when we come to instantiate it, we instantiate a reference to i which is invalid because i was not captured.

It looks like the problem is that we never do "full expression" checking on the operand of static_assert, so we don't ever promote the list of "potential ODR uses" from the expression into captures. That in turn means that even trivial perturbations of the source will make the bug go away by triggering the pending capture, such as:

  [&](auto) { static_assert(&i != nullptr); 0; }(0);