Возможен ли ленивый «оператор» или «перегрузка» с использованием синтаксиса размещения / кусочков? - PullRequest
5 голосов
/ 15 октября 2019

Итак, мы все были там. У меня есть хороший объект, у меня есть конструктор для него, объект может разрешить true, если он создан правильно, но у него также есть другое состояние, поэтому я могу с радостью написать:

if (const auto& str = std::ofstream("/tmp/myfile"))
{
  str << "Oh that was easy\n";
}

Но что бы я хотелочень нравится делать, это попробовать несколько альтернатив:

if (const std::stream& str = (std::ofstream("/tmp/myfile") || std::ofstream("/temp/myfile") || std::ofstream("./myfile")) )
{
  str << "Oh that was not so easy\n";
}

Очевидно, нет! Я потенциально открыл 3 файла, но привел их к «истинному» bool, и, конечно, не смог вернуться к std :: stream. Если я перегрузлю operator || для потока rvals, то он откроет все 3 файла, потому что ленивая оценка забыта!

Я знаю, что std :: stream, возможно, плохой пример, но спустя 20 страниц я закончу объяснение моего опционального процессора строк, поэтому давайте придерживаться std :: stream.

Что яможно было бы ожидать, что возможно будет что-то вроде:

if (const std::stream str = (std::piecewise_construct(std::ofstream,"/tmp/myfile") || std::piecewise_construct(std::ofstream,"/temp/myfile") || std::piecewise_construct(std::ofstream,"./myfile")) )
{
  str << "Oh that maybe makes sense?\n";
}

Чтобы сделать это, похоже, мне может понадобиться 2 перегрузки operator ||, одна из которых принимает две нерешенные piecewise_construct и разрешает левую для начала,и другой, который принимает объект rval слева и оценивает правую часть только в случае, если он «провален». Что-то вроде:

template<class PC2>
std::stream operator ||(std::stream&& str, const PC2& pc2)
{
  if (str)
    return std::move(str);
  return pc2.construct();
}

template<class PC1, class PC2>
std::stream operator ||(const PC1& pc1, const PC2& pc2)
{
  return operator||(pc1.construct(), pc2);
}

Where PCx here is the piecewise construct.

Но я сразу вижу, что std :: peicewise_construct - это просто тег. Сам по себе он не имеет никакой функциональности. Также я борюсь за то, чтобы найти какой-либо способ заставить эту работу работать с ограничениями шаблона FINAE: Нет особого смысла в создании двух несвязанных типов, которые нельзя перемещать. В C ++ существует множество подобных «хитростей», таких как std :: function или lambdas, которые пытаются выполнить аналогичную работу?

Прямо сейчас мне бы хотелось решение в C ++ 11, которое нене похоже на Франкенштейна после драки с его монстром, но не чувствуйте себя ограниченным, если нужная мне функция - всего лишь версия!

Между тем у меня есть 3 копии в основном одного и того же парсера и хранилища значений аргумента, потому чтоклиенты не могут договориться о том, какую опцию следует вызывать между версиями.

Полагаю, мне нужен какой-то настраиваемый шаблон, а не что-то жестко привязанное к конкретному классу, в данном случае std :: stream, ичетный поток имеет множество конструкторов. Достаточно распространенным шаблоном с дескрипторами является код, который в основном хочет сделать h = (open(O_APP) || open(O_CREATE) || open(O_TRUNC)) {за исключением того, что это C, а не объекты}.

Одним из способов продвижения вперед является некоторый шаблонный класс, который содержит std :: bind и знает, как создать, в свою очередь, созданный из вспомогательной функции. И, возможно, тип этого объекта обеспечит защиту типов, которую я тоже желаю.

И еще одно замечание ... В этом примере я использую std :: istream только с одним аргументом, но, конечно же, конструкторомfor istream может принимать несколько аргументов и иметь несколько альтернативных конструкторов, а мой внутренний сценарий использования имеет как минимум 3 и, возможно, больше опций в будущем, поэтому он действительно должен поддерживать тот шаблонный список произвольных аргументов, который нам всем нравится.

Ответы [ 2 ]

3 голосов
/ 15 октября 2019

Простейшая реализация, вероятно, заключается в написании функции, которая принимает диапазон имен файлов и возвращает первое, которое успешно открывается:

std::ofstream first_file_of(std::initializer_list<std::string> names) {
    for (auto&& name : names) {
        std::ofstream os(name);
        if (os) return os;
    }
    return std::ofstream();
}

Как в:

first_file_of({"/tmp/myfile", "/temp/myfile", "./myfile"})

Но если вы действительно хотите сходить с ума от операторов, вам нужен какой-то объект с некоторым перегруженным двоичным оператором, который выполняет этот вид условия.

Одна из таких реализаций, использующая operator|, будет выглядеть так:

struct or_else_file {
    std::string name;

    or_else_file(std::string name) : name(name) { }

    friend std::ofstream operator|(std::ofstream&& os, or_else_file oef) {
        if (os) {
            return std::move(os);
        } else {
            return std::ofstream(oef.name);
        }
    }
};

Как в:

std::ofstream("/tmp/myfile") | or_else_file("/temp/myfile") | or_else_file("./myfile")

Это более сложный и более подробный, но можно сказать, что читать лучше в зависимости от вашей точки зрения.

1 голос
/ 15 октября 2019

Попробуйте это:

template<class Tuple, std::size_t...Is>
struct try_make_t {
  Tuple inputs;
  template<std::size_t I, class T>
  bool try_make( std::optional<T>& out ) {
    out.emplace( std::get<I>(inputs) );
    return (bool)*out;
  }
  template<class T>
  operator T()&&{
    std::optional<T> retval;
    ( try_make<Is>( retval ) || ... );
    if (!retval)
      return {};
    return std::move(*retval);
  }
};
template<class...Ins, std::size_t...Is>
try_make_t< std::tuple<Ins&&...>, Is... > try_make_helper( std::index_sequence<Is...>, Ins&&...ins ) {
  return {std::tuple<Ins&&...>( std::forward<Ins>(ins)... )};
}
template<class...Ins>
auto try_make_from( Ins&&...ins ) {
  return try_make_helper( std::make_index_sequence<sizeof...(ins)>{}, std::forward<Ins>(ins)... );
}
int main() {
  if (std::ofstream&& str = try_make_from("/tmp/myfile", "/temp/myfile", "./myfile") )
  {
    str << "Oh that was easy\n";
  }
}

для более замысловатой конструкции

template<class F>
struct factory {
  F f;
  template<class T,
    std::enable_if_t< std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
  >
  operator T()&& { return f(); }
  template<class T,
    std::enable_if_t< std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
  >
  operator T()&& { return f(); }
  template<class T,
    std::enable_if_t< !std::is_convertible_v< decltype(std::declval<F&>()()), T>, bool> =true
  >
  operator T()&& { return std::make_from_tuple<T>(f()); }
};

, где factory{ []{ return something; } } создаст произвольный объект из возвращаемого значения лямбды или из кортежа, возвращенного излямбда.

if(
  std::ofstream&& file = try_make_from(
    factory{[]{return "/tmp/myfile";}},
    factory{[]{return std::make_tuple("/temp/myfile");}},
    "./myfile"
  )
)
...