Почему невозможно создать 'std :: filesystem :: path' из итераторов 'std :: filesystem :: path'?

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

#include <filesystem>

std::filesystem::path strip_prefix(std::filesystem::path p)
{      
  if (auto it{p.begin()}; it != p.end())
  {
    ++it;
    return std::filesystem::path(it, p.end());
  }

  return p;
}

(См.: https://godbolt.org/z/wkXhcw)

Я был удивлен, узнав, что это не работает. Код не компилируется, так как конструктор пути принимает только итераторы, которые перебирают последовательности символов. Я могу видеть использование этого, но зачем ограничивать конструкцию только такими итераторами? На мой взгляд, нелогично не поддерживать построение пути из собственных итераторов. Насколько я знаю, большинство других типов STL поддерживают эту идиому.

Что может быть эффективной реализацией для достижения той же цели, кроме полной реконструкции нового пути?

Обновление: в этом контексте я нашел следующее обсуждение актуальным/забавным: http://boost.2283326.n4.nabble.com/boost-filesystem-path-frustration-td4641734.html. Я согласен с Дейвом здесь. Я думаю, что видеть путь как контейнер элементов пути - это очень естественный способ взглянуть на него (с точки зрения программиста).

Ответ 1

Самое простое решение для объединения сегментов для создания нового path - это просто std::accumulate().

Для вашего конкретного случая использования я бы сделал что-то вроде этого:

std::filesystem::path strip_prefix(std::filesystem::path p)
{
    if(p.empty()) return p;
    return std::accumulate(std::next(p.begin()), p.end(), 
                           std::filesystem::path{}, std::divides{});
}

Что касается того, почему нет конструктора (или, возможно, свободной функции), чтобы сделать это? Я не знаю. Это похоже на необходимость, возникающую при работе с путями, но комитет, как правило, неохотно добавляет вспомогательные функции к стандартным классам, если тот же результат может быть достигнут с помощью вызова стандартного алгоритма.

Ответ 2

Какова будет эффективная реализация для достижения той же цели?

Самое простое решение:

std::filesystem::path strip_prefix(std::filesystem::path p)
{
    return relative(p, p.parent_path());
}

Смотрите в действии здесь

Почему нет конструктора

Я не знаю точную причину, почему нет конструктора для построения пути из кусочков. Но мой опыт работы с другими языками намекает на то, что такое построение пути из кусков переносимым способом, вероятно, невозможно.

Ответ 3

Это то, что вы пытаетесь достичь:

#include <filesystem>

std::filesystem::path strip_prefix(std::filesystem::path p)
{
    auto it{p.begin()};
    if (it != p.end())
    {
      ++it;
      return *it;
    }

    return p;
}

В противном случае я не уверен, какова ваша цель функции.

Ответ 4

Это невозможно, поскольку ни одна из перегрузок конструктора std :: filesystem :: path не принимает итераторы для пути, только итераторы для строки с нулевым символом в конце или итераторы для std::string. В вашем случае вы должны изменить сигнатуру функции на:

std::filesystem::path strip_prefix(std::string p)

Итераторы пути - это другой зверь. Они не представляют указатели на символы, они представляют:

1) root-name (если есть)
2) корневой каталог (если есть)
3) последовательность имен файлов без каких-либо разделителей каталогов
4) Если после последнего имени файла в пути есть разделитель каталогов, последний элемент перед конечным итератором является пустым элементом.