Domanda

I have been looking at creating a synchroniser helper template class which is based on Herb Sutter's ideas of a wrapper class in this talk This does not work in msvc as is (unless we remove the brace initialisation) but when brace initialisation is removed then it's fine.

In clang/gcc (ubuntu 12.10, gcc4.7.2, clang (3.2) self built with libc++) it seems the private access modifier has to appear before the public: which seems a little strange.

The error in gcc is error: ‘t_’ was not declared in this scope

and clang is

error: use of undeclared identifier 't_'
  auto operator()(F f) const ->decltype(f(t_))

It may be a template/declytpe issue that I am not aware of and wonder if anyone can help with this one. (all compiled with relevant c++11 flags)

template <class T>
class Synchronised {
    public:
        Synchronised(T t = T{}) : t_{t} {}
        template <typename F>
        auto operator()(F f) const -> decltype(f(t_)) {
            std::lock_guard<std::mutex> lock{mutex_};
            return f(t_);
        }
        private: // place this before public: and this object compiles
            mutable T t_;
            mutable std::mutex mutex_;
};

Edit: Adding Johannes's ideas and full class in case anyone wants a cut and paste.

#include <future>
#include <iostream>
#include <thread>
#include <vector>

template <class T> T &self(T &t) { return t;  }
template<typename T> struct Dependent {  };

template<typename T>
class Synchronised : Dependent<T>{
 public:
  explicit Synchronised(T t = T()) : t_(t) {}
  template<typename Functor>
  auto operator()(Functor functor) const ->decltype(functor(self(*this).t_)) {
  //auto operator()(Functor functor) const ->decltype(functor(this->t_)) {
    std::lock_guard<std::mutex> lock(mutex_);
    return functor(t_);
  }
 private:
  mutable T t_;
  mutable std::mutex mutex_;
};


int main() {

    Synchronised<std::string> sync_string("Start\n");
    std::vector<std::future<void>> futures;
}
È stato utile?

Soluzione

The below was only sufficient for making the class template definition itself valid. However the same rules that made the lookup not find the data member in the class template (which necessiated the introduction of the empty dependent base class or the dependent function call) will also make the instantiation of the class template not find the data member, and thereby will trigger a compiler error.

We told the compiler "hold on, perhaps you will find the data member at instantiation time", but I did not think about what will happen when actually instantiating. We will now make it so that the name is still dependent even after instantiation of the class happened. The resolution will have to wait until the call to operator().

// keep this little util somewhere :)
template<typename T>
struct self { 
  template<typename U> U &operator()(U &t) { return t; } 
};

template <class T>
class Synchronised {
    public:
// ...
        auto operator()(F f) const -> decltype(f(self<F>()(*this).t_)) {
// ...
};

The use of a class template for self instead of a function template will also prevent argument dependent lookup from happening, preventing that the author of F also writes a function called self that matches the argument *this (this could have been a potential problem with the partial solution below, too).


You have several other options, beside reordering

  1. Making the expression on the left side of . dependent, but not just the enclosing class (because it will be special-cased)

    // keep this little util somewhere :)
    template <class T> T &self(T &t) { return t; }
    
    template <class T>
    class Synchronised {
        public:
    // ...
            auto operator()(F f) const -> decltype(f(self(*this).t_)) {
    // ...
    };
    
  2. Introduce a dependent base class to work-around the special casing of the enclosing class

    // Keep this little util somewhere
    template<typename T> struct Dependent { };
    
    template <class T>
    class Synchronised : Dependent<T> {
        public:
    // ...
            auto operator()(F f) const -> decltype(f(this->t_)) {
    // ...
    };
    

The first is based on the Standard making self(*this).t_ a member of an unknown specialization

  • the type of the object expression is dependent and is not the current instantiation.

The second is based on the Standard making this->t_ a member of an unknown specialization

  • the type of the object expression is the current instantiation, the current instantiation has at least one dependent base class, and name lookup of the id-expression does not find a member of the current instantiation or a non-dependent base class thereof;

This in turn makes x->t_ for both cases a dependent expression and hence the name will be looked up at instantiation time. The Standard says

A class member access expression (5.2.5) is type-dependent if the expression refers to a member of the current instantiation and the type of the referenced member is dependent, or the class member access expression refers to a member of an unknown specialization.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top