0

For the following simple class:

#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;
}

Why does the constness in the following lambdas seem to get ignored? Shouldn't both assignments to fArg.ptr[0] cause a compilation failure?

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;
}

When compiled with:

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

(and also in http://coliru.stacked-crooked.com)

It outputs this:

----- 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]

Please advise.

PS The above is motivated by trying to understand how std::thread constructor arguments get stored and constructed. Reference: Why the compiler complains that std::thread arguments must be invocable after conversion to rvalues?

As explained above, I was expecting 2 compilation failures due to the constness of the lambda args

2
  • 3
    The pointer is const, not the thing it points to. When a Foo_t object is const, it's ptr member is a char* const, not a char const * or a char const * const. Commented Nov 12, 2023 at 5:42
  • Side note: instead of allocate and copy, it's better to use std::strdup/strndup with std::free for deallocation. Commented Nov 12, 2023 at 8:10

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.