Program Listing for File singleton.hpp

Return to documentation for file (SeQuant/core/utility/singleton.hpp)

//
// Created by Eduard Valeyev on 2019-03-11.
//

#ifndef SEQUANT_CORE_UTILITY_SINGLETON_HPP
#define SEQUANT_CORE_UTILITY_SINGLETON_HPP

#include <cassert>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <type_traits>

namespace sequant {

// clang-format off
// clang-format on
template <typename Derived>
class Singleton {
  // can't use std::is_default_constructible since Derived's ctors should be
  // private
  template <typename T, typename Enabler = void>
  struct is_default_constructible_helper : public std::false_type {};
  template <typename T>
  struct is_default_constructible_helper<T, std::void_t<decltype(T{})>>
      : public std::true_type {};
  constexpr static bool derived_is_default_constructible =
      is_default_constructible_helper<Derived>::value;

 public:
  static Derived& instance() {
    const auto& result_ptr = instance_accessor();
    if (result_ptr != nullptr) return *result_ptr;
    if constexpr (derived_is_default_constructible) {
      return set_default_instance();
    } else
      throw std::logic_error(
          "sequant::Singleton: is not default-constructible and set_instance() "
          "has not been called");
  }

  static Derived* instance_ptr() {
    const auto& result_ptr = instance_accessor();
    if (result_ptr != nullptr) return result_ptr.get();
    if constexpr (derived_is_default_constructible) {
      return set_default_instance();
    } else
      return nullptr;
  }

  template <typename... Args>
  static Derived& set_instance(Args&&... args) {
    //    WARNING: can't check constructibility since the ctor may be private
    //    static_assert(std::is_constructible_v<Derived, Args...>,
    //                  "sequant::Singleton::set_instance: Derived is not
    //                  constructible with Args");
    std::scoped_lock lock(instance_mutex());
    if (instance_accessor() != nullptr)
      throw std::logic_error(
          "sequant::Singleton::set_instance: instance has already been "
          "constructed");
    instance_accessor() = std::move(
        std::unique_ptr<Derived>(new Derived(std::forward<Args>(args)...)));
    return *instance_accessor();
  }

 protected:
  template <typename... Args>
  Singleton(Args&&... args) {}  // all constructors are private

  static auto& instance_accessor() {
    static std::unique_ptr<Derived> instance;
    return instance;
  }
  // provides mutex that controls access to instance_accessor()'s object
  static auto& instance_mutex() {
    static std::mutex mtx;
    return mtx;
  }

 private:
  static Derived& set_default_instance() {
    std::scoped_lock lock(instance_mutex());
    if (!instance_accessor()) {
      instance_accessor() = std::move(std::unique_ptr<Derived>(new Derived));
    }
    return *instance_accessor();
  }
};

}  // namespace sequant

#endif  // SEQUANT_CORE_UTILITY_SINGLETON_HPP