将 std::reference_wrapper 转发到 lambda 参数时忽略常量

Constness ignored when forwarding std::reference_wrapper to lambda argument

提问人:user12655242 提问时间:11/12/2023 最后编辑:Toby Speightuser12655242 更新时间:11/12/2023 访问量:55

问:

对于以下简单类:

#include <iostream>
#include <string>
#include <thread>
#include <cstring>

struct Foo_t { 
    int size; 
    char *ptr; 

    Foo_t(int s = 0):size(s), 
                   ptr(size? new char[size]:nullptr) {
                       std::cout << "\nDefault Ctor...\n";
                   } 

    Foo_t(const Foo_t& obj): size(obj.size), 
                        ptr(size? new char[size]:nullptr) {
        memmove(ptr, obj.ptr, size*sizeof(char));
        std::cout << "Move Ctor... [" << (void*)this << ", ptr " << (void*)ptr << "| from " << (void*)&obj << "]\n";
    }

    Foo_t(Foo_t&& obj): size(obj.size), 
                        ptr(obj.ptr) { // Move constructor 
        obj.ptr = nullptr; 
        std::cout << "Move Ctor... [" << (void*)this << ", ptr " << (void*)ptr << "| from " << (void*)&obj << "]\n";
    }
    
    Foo_t(const char* ptr_) : size((ptr_ != nullptr) ? (strlen(ptr_) + 1) : 0),
                              ptr(size? new char[size]:nullptr)  {
         strcpy(ptr, ptr_);
         std::cout << "Overload Ctor... [" << (void*)this << "| ptr " << (void*)ptr << "]\n";
    }
    
    void swap(Foo_t& obj1, Foo_t& obj2) { 
        std::swap(obj1.size, obj2.size); 
        std::swap(obj1.ptr, obj2.ptr); 
    } 
    
    Foo_t& operator=(Foo_t obj) {
        std::cout << "Generalized assignment to [" << (void*)this << "], from [" << &obj <<"]...\n";
        swap(*this, obj); 
        return *this; 
    } 
    
    ~Foo_t() { 
        delete[] ptr; 
        std::cout << "Dtor... [" << (void*)this << "| ptr: " << (void*)ptr << "]\n";
    }
    friend std::ostream& operator<<(std::ostream& os, const Foo_t& ft_);
};

bool operator==(const Foo_t& lhs_, const Foo_t& rhs_) {
    return !strcmp(lhs_.ptr, rhs_.ptr);
}

std::ostream& operator<<(std::ostream& os, const Foo_t& ft_) {
    os << (ft_.ptr ? std::string(ft_.ptr) : std::string()); 
    return os;
}

为什么以下 lambda 中的恒常性似乎被忽略了?对 fArg.ptr[0] 的两个赋值都不应该导致编译失败吗?

int main() {
  std::cout << "\n----- auto threadFuncExample4(threadStorage4); //const Foo_t& arg -------\n";
  auto ft4{Foo_t("Foo_t example4")};
  auto threadStorage4{std::reference_wrapper<Foo_t>{ft4}};
  auto threadFuncExample4 = [](const Foo_t& fArg){
                              std::cout << "\nthreadFuncExample4 [" << fArg.size << "]\n";
                              fArg.ptr[0] = 'W';  //This should cause a compilation failure
                           };
  threadFuncExample4(std::forward<Foo_t>(threadStorage4));
  std::cout << "threadStorage4 [" << threadStorage4 << "]\n";

  std::cout << "\n----- std::thread([](const Foo_t& fArg){}, std::ref(ft11)); -------\n";
  Foo_t ft11(Foo_t("Foo_t arg"));
  std::thread t11([](const Foo_t& fArg){
                    std::cout << "\nt11 [" << fArg.size << "]... tid[" << std::this_thread::get_id() << "]\n";
                    fArg.ptr[0] = 'W'; //This should cause a compilation failure
                },
                std::ref(ft11));
  t11.join();
  std::cout << "ft11 [" << ft11 << "]\n";
  return 0;
}

编译时:

bash-3.2$ g++ -g -Wall -std=c++2a ThreadArgs.C -o TA.out 
bash-3.2$ g++ -v Apple clang version 15.0.0 (clang-1500.0.40.1) 
Target: x86_64-apple-darwin23.0.0

(也在 http://coliru.stacked-crooked.com)

它输出如下:

----- auto threadFuncExample4(threadStorage4); //const Foo_t& arg -------
Overload Ctor... [0x7ffe6e435a00| ptr 0x1348030]

threadFuncExample4 [15]
threadStorage4 [Woo_t example4]

----- std::thread([](const Foo_t& fArg){}, std::ref(ft11)); -------
Overload Ctor... [0x7ffe6e435a10| ptr 0x1348050]

t11 [13]... tid[139705825490688]
ft11 [Woo_t arg]
Dtor... [0x7ffe6e435a10| ptr: 0x1348050]
Dtor... [0x7ffe6e435a00| ptr: 0x1348030]

请指教。

PS 以上内容的动机是试图理解 std::thread 构造函数参数是如何存储和构造的。参考:为什么编译器抱怨 std::thread 参数在转换为 rvalues 后必须是可调用的?

如上所述,由于 lambda 参数的恒定性,我预计会出现 2 次编译失败

C++ C++20 移动语义 stdthread

评论

3赞 Miles Budnek 11/12/2023
指针是 ,而不是它所指向的事物。当一个对象是 时,它的成员是 ,而不是 或 。constFoo_tconstptrchar* constchar const *char const * const
0赞 Red.Wave 11/12/2023
旁注:与其分配和复制,不如使用 / with 进行解除分配。std::strdupstrndupstd::free

答: 暂无答案