Check if a function argument value is thread-local

149 views Asked by At

In C++, is it possible to check (preferably at compile time) if a function argument reference value is thread-local?

E.g.

void foo( int& bar ) { ... }

I'd like to check/enforce that bar refer to a thread_local.

Assume C++17 or later on Windows and/or Fedora.

2

There are 2 answers

7
Yakk - Adam Nevraumont On BEST ANSWER
template<class T, auto Key>
struct thread_local_decl_t;

template<class T>
struct thread_local_t {
  template<class, auto>
  friend struct thread_local_decl_t;
  operator T&() noexcept {
    return get();
  }
  operator T const&() const noexcept {
    return get_const();
  }
private:
  T&(*gettor)() = nullptr;
  thread_local_t(T&(*gettor_arg)()):
    gettor(gettor_arg)
  {}
  T& get() { return gettor(); }
  T const& get_const() const { return gettor(); }
};
template<class T, auto Key>
struct thread_local_decl_t:
  thread_local_t<T>
{
public:
  static T& get_ctor(std::function<T()> ctor) {
    thread_local T t{ctor()};
    return t;
  }
  static T& get() {
    return get_ctor(nullptr);
  }
  static T const& get_const() {
    return get(nullptr);
  }
  template<class...Args>
  explicit thread_local_decl_t(Args&&...args):
    thread_local_t<T>(&thread_local_decl_t::get) 
  {
    // construct the thread-local
    get_ctor( [&]()->T{ return T(std::forward<Args>(args)...); } );
  }
  thread_local_decl_t(thread_local_decl_t const&)=default;
  thread_local_decl_t(thread_local_decl_t &&)=default;

};

#define THREAD_LOCAL(...) \
  thread_local_decl_t< __VA_ARGS__, []{} >


void print( thread_local_t<int> x ) {
  std::cout << x << "\n";
}

void test() {
  auto tl_int = THREAD_LOCAL(int)( 7 );
  print(tl_int);
}
int main() {
  test();
}

this bit of evil uses a lambda to create a thread local object within the thread_local_decl_t that is unique to each use site. We can pass thread local values around and verify they are indeed thread local with the C++ type system by doing a thread_local_t<int>.

thread_local_t<X> is a reference to a thread local value, while auto name = THREAD_LOCAL(X)(construction arguments) declares a thread local value, using this system.

Requires .

2
Pepijn Kramer On

No you cannot check at compile time. However you can do something to at least document intent and do some compile time checking like this (by letting the type system do some work for you):

#include <iostream>

// Declare/define a class that can behave stand in for type_t
// but also describes intent
// more constructors/destructors might need to be added... this is just a sketch
template<typename type_t>
class thread_local_t
{
public:
    explicit thread_local_t(const type_t& value) :
        m_value{value}
    {
    }

    operator type_t&() noexcept
    {
        return m_value;
    }

    operator const type_t&() const noexcept
    {
        return m_value;
    }

private:
    type_t m_value;
};

// accept only a thread_local_t<int> (decorated) int
// since thread_local_t is not implicitly convertible from int
// this adds an extra compile time barrier
int foo(const thread_local_t<int>& value)
{
    std::cout << value;
    return value;
}

int main()
{
    thread_local thread_local_t<int> value{42}; // <== create a thread_local "int" here
    auto retval = foo(value);
    // foo(42);  <== will not compile
    return retval;
}