Question

Yesterday it took me ages to come to grips with a compile-time error caused by calling a const member function from a non-const object as in this example:

// my utility header
template<typename derived>
struct crtp_task : task
{
  std::list<task*> list;

  // note: cannot be const, as task::allocate_child() isn't
  template<typename... Args>      
  void add_child(Args&&... args)
  {
    list.push_back(new(task::allocate_child())
                   derived(std::forward<Args>(args)...));
  }
  /* ... */
};

// an application
struct my_task : crtp_task<my_task>
{
  some_data data;
  /* ... */
  my_task(some_data d) data(d) {}
  void generate_children() const    // constant member
  {
    /* ... */
    some_data child_data = /* ... */;
    this->add_child(child_data);    // error: cannot call non-const member
  }
};

The clang error message was several lines and too cryptic (not mentioning const), but gcc came up with a better error (though even more lines, but eventually complaining about me ignoring cv qualifiers).

So, to avoid this sort of thing in the future, I thought about using static_assert() in my utility header. My naive approach

// my utility header
template<typename derived>
struct crtp_task : task
{
  std::list<task*> list;

  // note: cannot be const, as task::allocate_child() isn't
  template<typename... Args>      
  void add_child(Args&&... args)
  {
    list.push_back(new(task::allocate_child())
                   derived(std::forward<Args>(args)...));
  }

  // note: catch call from const objects
  template<typename... Args>      
  void add_child(Args&&...) const
  {
    static_assert(false,"add_child() const called");
  }      
  /* ... */
};

fails, as the compiler immediately triggers an error, even if the template void add_child() const is never called. How else can I make this work?

Was it helpful?

Solution

If you want some overload to cause a compilation error when it gets selected in overload resolution, you can use the new = delete feature.

template<typename... Args>
void add_child(Args&&...) const = delete;

This will generate a nice simple error along the lines of "use of deleted function void add_child(Args&&...) const".

OTHER TIPS

static_assert must depend on a template parameter to be delayed after the instantiation of the template. Otherwise they will be "called" as soon as all information is available, in your case the false is ready pretty early in compilation.

To solve this, I usually make it "artificially" depend on a template parameter just like in:

template<class FOO>
void foo( const FOO& )
{
    static_assert(sizeof(FOO)==0,"This function should never be instantiated");
}

Maybe in your case just deleting the function might be the better approach though.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top