Skip to content

clang-tidy always emits clang-analyzer-core.CallAndMessage warning for reference captured structured bindings in lambdas #142267

@justusranvier

Description

@justusranvier

This is allowed in C++20 by P1091R3 and implemented since #54300 however in every case I've found using these constructs clang-tidy emits uninitialized variable warnings.

code example:

#include <functional>
#include <list>
#include <map>
#include <string>

void maybe_consume(std::string const&, std::list<std::string>&);

void example()
{
    auto var = std::map<std::string, std::list<std::string>>{};

    for (auto i = var.begin(); i != var.end();) {
        auto& [key, value] = *i;
        auto p1091r3 = [&] { maybe_consume(key, value); };
        std::invoke(p1091r3);

        if (value.empty()) {
            i = var.erase(i);
        } else {
            ++i;
        }
    }
}

compiler output: https://godbolt.org/z/ns7Yc7GT8

clang-tidy command line: clang-tidy --checks="clang-analyzer-*" --extra-arg="-std=c++20"

clang-tidy output:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&] { maybe_consume(key, value); };
      |                              ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke<(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke<(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
  122 |       return std::__invoke(std::forward<_Callable>(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward<_Args>(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl<void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
   98 |       return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward<_Args>(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&] { maybe_consume(key, value); };
      |                              ^             ~~~

Switching from default capture to named capture does not help:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&key, &value] { maybe_consume(key, value); };
      |                                         ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke<(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke<(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
  122 |       return std::__invoke(std::forward<_Callable>(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward<_Args>(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl<void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &, >'
   98 |       return std::__invoke_impl<__type>(__tag{}, std::forward<_Callable>(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward<_Args>(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward<_Fn>(__f)(std::forward<_Args>(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&key, &value] { maybe_consume(key, value); };
      |                                         ^             ~~~

Activity

llvmbot

llvmbot commented on May 31, 2025

@llvmbot
Member

@llvm/issue-subscribers-clang-tidy

Author: Justus Ranvier (justusranvier)

This is allowed by P1091R3 and implemented since #54300 however in every case I've found using these constructs clang-tidy emits uninitialized variable warnings.

code example:

#include &lt;functional&gt;
#include &lt;list&gt;
#include &lt;map&gt;
#include &lt;string&gt;

void maybe_consume(std::string const&amp;, std::list&lt;std::string&gt;&amp;);

void example()
{
    auto var = std::map&lt;std::string, std::list&lt;std::string&gt;&gt;{};

    for (auto i = var.begin(); i != var.end();) {
        auto&amp; [key, value] = *i;
        auto p1091r3 = [&amp;] { maybe_consume(key, value); };
        std::invoke(p1091r3);

        if (value.empty()) {
            i = var.erase(i);
        } else {
            ++i;
        }
    }
}

compiler output: https://godbolt.org/z/ns7Yc7GT8

clang-tidy command line: clang-tidy --checks="clang-analyzer-*" --extra-arg="-std=c++20"

clang-tidy output:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&amp;] { maybe_consume(key, value); };
      |                              ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
  122 |       return std::__invoke(std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward&lt;_Args&gt;(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl&lt;void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   98 |       return std::__invoke_impl&lt;__type&gt;(__tag{}, std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward&lt;_Args&gt;(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward&lt;_Fn&gt;(__f)(std::forward&lt;_Args&gt;(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&amp;] { maybe_consume(key, value); };
      |                              ^             ~~~

Switching from default capture to named capture does not help:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&amp;key, &amp;value] { maybe_consume(key, value); };
      |                                         ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
  122 |       return std::__invoke(std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward&lt;_Args&gt;(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl&lt;void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   98 |       return std::__invoke_impl&lt;__type&gt;(__tag{}, std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward&lt;_Args&gt;(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward&lt;_Fn&gt;(__f)(std::forward&lt;_Args&gt;(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&amp;key, &amp;value] { maybe_consume(key, value); };
      |                                         ^             ~~~
llvmbot

llvmbot commented on May 31, 2025

@llvmbot
Member

@llvm/issue-subscribers-clang-static-analyzer

Author: Justus Ranvier (justusranvier)

This is allowed in C++20 by P1091R3 and implemented since #54300 however in every case I've found using these constructs clang-tidy emits uninitialized variable warnings.

code example:

#include &lt;functional&gt;
#include &lt;list&gt;
#include &lt;map&gt;
#include &lt;string&gt;

void maybe_consume(std::string const&amp;, std::list&lt;std::string&gt;&amp;);

void example()
{
    auto var = std::map&lt;std::string, std::list&lt;std::string&gt;&gt;{};

    for (auto i = var.begin(); i != var.end();) {
        auto&amp; [key, value] = *i;
        auto p1091r3 = [&amp;] { maybe_consume(key, value); };
        std::invoke(p1091r3);

        if (value.empty()) {
            i = var.erase(i);
        } else {
            ++i;
        }
    }
}

compiler output: https://godbolt.org/z/ns7Yc7GT8

clang-tidy command line: clang-tidy --checks="clang-analyzer-*" --extra-arg="-std=c++20"

clang-tidy output:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&amp;] { maybe_consume(key, value); };
      |                              ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
  122 |       return std::__invoke(std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward&lt;_Args&gt;(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl&lt;void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   98 |       return std::__invoke_impl&lt;__type&gt;(__tag{}, std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward&lt;_Args&gt;(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward&lt;_Fn&gt;(__f)(std::forward&lt;_Args&gt;(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:30: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&amp;] { maybe_consume(key, value); };
      |                              ^             ~~~

Switching from default capture to named capture does not help:

1 warning generated.
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: warning: 1st function call argument is an uninitialized value [clang-analyzer-core.CallAndMessage]
   14 |         auto p1091r3 = [&amp;key, &amp;value] { maybe_consume(key, value); };
      |                                         ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:12:5: note: Loop condition is true.  Entering loop body
   12 |     for (auto i = var.begin(); i != var.end();) {
      |     ^
/home/user/src/examples/clang-analyzer-p1091r3.cpp:15:9: note: Calling 'invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   15 |         std::invoke(p1091r3);
      |         ^~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/functional:122:14: note: Calling '__invoke&lt;(lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
  122 |       return std::__invoke(std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  123 |                            std::forward&lt;_Args&gt;(__args)...);
      |                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:98:14: note: Calling '__invoke_impl&lt;void, (lambda at /home/user/src/examples/clang-analyzer-p1091r3.cpp:14:24) &amp;, &gt;'
   98 |       return std::__invoke_impl&lt;__type&gt;(__tag{}, std::forward&lt;_Callable&gt;(__fn),
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   99 |                                         std::forward&lt;_Args&gt;(__args)...);
      |                                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/lib/gcc/powerpc64le-unknown-linux-gnu/15/include/g++-v15/bits/invoke.h:63:14: note: Calling 'operator()'
   63 |     { return std::forward&lt;_Fn&gt;(__f)(std::forward&lt;_Args&gt;(__args)...); }
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/user/src/examples/clang-analyzer-p1091r3.cpp:14:41: note: 1st function call argument is an uninitialized value
   14 |         auto p1091r3 = [&amp;key, &amp;value] { maybe_consume(key, value); };
      |                                         ^             ~~~
steakhal

steakhal commented on Jun 1, 2025

@steakhal
Contributor

Could you reproduce it on Godbolt using the trunk clang tidy?
I think we may have fixed this already in b55dd8f, which should be available on main.

justusranvier

justusranvier commented on Jun 1, 2025

@justusranvier
Author

If it's possible to run clang static analyzer on Compiler Explorer I don't know how to so, but it does seem likely this issue is a duplicate of #91835.

I'll test again the next time I get a chance to build LLVM from source or when version 21 is released, whichever comes first.

steakhal

steakhal commented on Jun 1, 2025

@steakhal
Contributor

If it's possible to run clang static analyzer on Compiler Explorer I don't know how to so, but it does seem likely this issue is a duplicate of #91835.

I think it's that issue. It's because prior to that patch, lambdas could not capture decomposed decls (aka. structured bindings), hence they remained associated with the default, uninitialized state, hence the report of 1st function call argument is an uninitialized value when it tries to read the decomposed decl key.

I'll test again the next time I get a chance to build LLVM from source or when version 21 is released, whichever comes first.

Here is an example configuration: https://godbolt.org/z/E3Eh8PKzG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @justusranvier@steakhal@llvmbot@vbvictor

        Issue actions

          clang-tidy always emits clang-analyzer-core.CallAndMessage warning for reference captured structured bindings in lambdas · Issue #142267 · llvm/llvm-project