skip to Main Content

The following code…

// main.cpp
#include <grpcpp/grpcpp.h>

class ClientContextContainer {
public:
  ClientContextContainer( int i ) : i_(i) {}

private:
  grpc::ClientContext client_context_;
  int i_;
};

class ArrayContainer {
public:
  ArrayContainer() : ccc_{ {42}, {1138} } {}

private:
  ClientContextContainer ccc_[2];
};

int main( int argc, char* argv[] ) {
  ArrayContainer ac;

 return 0;
}

…generates this error:

root@178258c7c52d:/tmp# /usr/bin/x86_64-linux-gnu-g++ --version && /usr/bin/x86_64-linux-gnu-g++ -c ./main.cpp
x86_64-linux-gnu-g++ (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

./main.cpp: In constructor 'ArrayContainer::ArrayContainer()':
./main.cpp:15:41: error: use of deleted function 'ClientContextContainer::ClientContextContainer(ClientContextContainer&&)'
   ArrayContainer() : ccc_{ {42}, {1138} } {}
                                         ^
./main.cpp:4:7: note: 'ClientContextContainer::ClientContextContainer(ClientContextContainer&&)' is implicitly deleted because the default definition would be ill-formed:
 class ClientContextContainer {
       ^~~~~~~~~~~~~~~~~~~~~~
./main.cpp:4:7: error: 'grpc::ClientContext::ClientContext(const grpc::ClientContext&)' is private within this context
In file included from /usr/local/include/grpcpp/client_context.h:37,
                 from /usr/local/include/grpcpp/grpcpp.h:53,
                 from ./main.cpp:2:
/usr/local/include/grpcpp/impl/codegen/client_context.h:388:3: note: declared private here
   ClientContext(const ClientContext&);
   ^~~~~~~~~~~~~

I do not fully understand the compiler error, but by my reading, it implies that the problem is that class ClientContextContainer‘s move constructor is implicitly deleted because the class’ member grpc::ClientContext object has a private copy constructor. (Inspection of client_context.h shows that its assignment operator is also private).

Fine. But if that’s the case, why can’t I reproduce the compile error if I replace the grpc::ClientContext member object with my own object that also has a private copy constructor (and assignment operator)?

// main.cpp
//#include <grpcpp/grpcpp.h>

class FauxClientContext {
public:
  FauxClientContext() {}

private:
  FauxClientContext( const FauxClientContext& );
  FauxClientContext& operator=( const FauxClientContext& );
};

class ClientContextContainer {
public:
  ClientContextContainer( int i ) : i_(i) {}

private:
  FauxClientContext client_context_;
  //grpc::ClientContext client_context_;
  int i_;
};

class ArrayContainer {
public:
  ArrayContainer() : ccc_{ {42}, {1138} } {}

private:
  ClientContextContainer ccc_[2];
};

int main( int argc, char* argv[] ) {
  ArrayContainer ac;

 return 0;
}
root@178258c7c52d:/tmp# /usr/bin/x86_64-linux-gnu-g++ --version && /usr/bin/x86_64-linux-gnu-g++ -c ./main.cpp
x86_64-linux-gnu-g++ (Debian 8.3.0-6) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

root@178258c7c52d:/tmp#

What’s the true nature of the error compiling the first version of main.cpp? That I could successfully compile the second version of main.cpp implies that it’s not what I interpreted the compiler error message to mean.


To those familiar with grpc, the root of this line of inquiry is: is it possible to construct an array of objects, in an initializer-list, if the objects have a member grpc::ClientContext object?

2

Answers


  1. My original post contained too much speculation and further analysis of your issue, the first update of my answer contained a misassumption about the actual underlying issues here further on unfortunately. So this answer should be the final version and addresses all relevant issues here hopefully.

    When are move constructors implicitly deleted:

    Excerpt from https://en.cppreference.com/w/cpp/language/move_constructor

    The implicitly-declared or defaulted move constructor for class T is
    defined as deleted if any of the following is true:

    • T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
    • T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
    • T has direct or virtual base class with a deleted or inaccessible destructor;
    • T is a union-like class and has a variant member with non-trivial move constructor.

    A defaulted move constructor that is deleted is ignored by overload
    resolution (otherwise it would prevent copy-initialization from
    rvalue).

    Why you get a compile error:

    According to the standard at least already since C++11, you shouldn’t actually since you use the C++11 direct list initialization syntax (see your array) with a well defined constructor for your case correctly. Copy and move constructors are not required here for your case at least. g++ has a still not fixed bug (almost historical now) that causes your problems here, see

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63707

    Your compiler works standard conformably (as far as I was able to test it) at least as long as your class ClientContextContainer doesn’t hold a member whose according class destructor isn’t implicitly (default) defined. That’s not the case for grpc::ClientContext since it has several members (shared pointers) with non-default destructors. That’s why, your modified code snippet with this simple class works well even for the case where all copy/move constructors are private/deleted. Try to introduce a critical member here in terms of the destructor and you’ll be able to reproduce the compile error quickly.

    Side note:

    Your actual issue here has nothing to do with guaranteed copy elision – introduced in C++17 (that was my misassumption here actually). Maybe it has an indirect relevance here with focus on this g++ bug since if the bug is a missing direct initialization but an illegal underlying usage of copy-initialization, g++ also would violate the guaranteed copy elision requirement of the standard since C++17 here for your case I think.

    For further information, see

    https://en.cppreference.com/w/cpp/language/direct_initialization

    https://en.cppreference.com/w/cpp/language/list_initialization

    https://en.cppreference.com/w/cpp/language/copy_initialization

    https://en.cppreference.com/w/cpp/language/copy_elision

    Login or Signup to reply.
  2. tl;dr: It’s a compiler bug.

    This is a bug in the GCC compiler:

    Brace initialization of array sometimes fails if no copy constructor (gcc.gnu.org/bugzilla)

    It has been fixed, as of the time of writing, for GCC 11.0, 10.3 and 9.4, but manifests in currently released versions upto and including 10.2.

    A reduced testcase due to Jonathan Wakeley:

    struct NonCopyable {
      NonCopyable(const NonCopyable&) = delete;
      NonCopyable(NonCopyable&&) = delete;
      NonCopyable& operator=(const NonCopyable&) = delete;
      NonCopyable& operator=(NonCopyable&&) = delete;
    
      NonCopyable() {}
    
      ~NonCopyable() {} // makes the destructor non-trivial
    };
    
    struct A {
      A(): _a{} {}
      ~A() {}
    
      NonCopyable _a[5];
    } a;
    

    This works with clang, fails with GCC (see it on GodBolt).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search