Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

What's Clang's problem with my code? GCC and MSVC think it's fine

I’m working on an Advent of Code challenge (2021 day 18). Just as a test I tried compiling it on different compilers. While GCC (11.2) and MSVC (19.30) think it’s fine, Clang (13.0.0) throws a list of errors. link to compiler explorer

/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/alloc_traits.h:514:4: error: no matching function for call to 'construct_at'
          std::construct_at(__p, std::forward<_Args>(__args)...);
          ^~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/vector.tcc:115:21: note: in instantiation of function template specialization 'std::allocator_traits<std::allocator<RegularNumber>>::construct<RegularNumber, int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>' requested here
            _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                           ^
<source>:115:19: note: in instantiation of function template specialization 'std::vector<RegularNumber>::emplace_back<int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>' requested here
        regNumVec.emplace_back(*it - '0', leftRegNumIdx,
                  ^
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/stl_construct.h:94:5: note: candidate template ignored: substitution failure [with _Tp = RegularNumber, _Args = <int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>]: no matching constructor for initialization of 'RegularNumber'
    construct_at(_Tp* __location, _Args&&... __args)
    ^

What doesn’t Clang understand what seems fine according to the other two? Is this a bug, or my mistake?

Here’s the "minimalized" code:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

#include <algorithm>
#include <concepts>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

template <typename Type, typename Description>
class Named {
   public:
    using UnderlyingType = Type;

    // clang-format off
  constexpr Named()
    noexcept(std::is_nothrow_default_constructible_v<Type>)
    requires std::default_initializable<Type>
  = default;
  explicit constexpr Named(Type const &val)
    noexcept(std::is_nothrow_copy_constructible_v<Type>) 
    requires std::copyable<Type>
    : val_{val}
  {}
  explicit constexpr Named(Type&& val)
    noexcept(std::is_nothrow_move_constructible_v<Type>)
    requires std::movable<Type>
    : val_{std::move(val)}
  {}

  constexpr operator Type() const&
    noexcept(std::is_nothrow_copy_constructible_v<Type>)
    requires std::copyable<Type>
  {
    return val_;
  }
  constexpr operator Type() const&&
    noexcept(std::is_nothrow_move_constructible_v<Type>) // move assignable?
    requires std::movable<Type>
  {
    return std::move(val_);
  }

 private:
  Type val_;
};
// clang-format on

template <typename T>
auto readinputdata(std::istream &&is) {
    auto data{std::vector<T>{}};
    copy(std::istream_iterator<T>{is}, std::istream_iterator<T>{},
         back_inserter(data));
    return data;
}

static constexpr auto testData{
    "[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]\n"
    "[[[5,[2,8]],4],[5,[[9,9],0]]]\n"
    "[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]\n"
    "[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]\n"
    "[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]\n"
    "[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]\n"
    "[[[[5,4],[7,7]],8],[[8,3],8]]\n"
    "[[9,3],[[9,9],[6,[4,9]]]]\n"
    "[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]\n"
    "[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]\n"};

struct index_for_vector_of_Pair;
using PairIndex = Named<int, index_for_vector_of_Pair>;

struct index_for_vector_of_RegularNumber;
using RegNumIndex = Named<int, index_for_vector_of_RegularNumber>;

struct RegularNumber {
    using IndexType = RegNumIndex;

    int value{};
    RegNumIndex leftIdx{}, rightIdx{};
};

using IndexVariant = std::variant<PairIndex, RegNumIndex>;

struct Pair {
    using IndexType = PairIndex;

    PairIndex parent{};
    IndexVariant leftIdx{}, rightIdx{};
};

static auto pairVec{std::vector<Pair>{}};
static auto regNumVec{std::vector<RegularNumber>{}};

// used for converting the input strings to pairs
static auto leftRegNumIdx{RegNumIndex{-1}};

static constexpr auto invalid_index{-1};

template <typename Type>
auto get_last_index(std::vector<Type> const &vec) {
    using Index = typename Type::IndexType;
    return Index{static_cast<typename Index::UnderlyingType>(size(vec))};
}

auto generate_pair(std::string::const_iterator &it, PairIndex parent)
    -> PairIndex;

auto generate_side(std::string::const_iterator &it, PairIndex pairIdx)
    -> IndexVariant {
    if (*it == '[') {
        return generate_pair(it, pairIdx);
    } else {
        regNumVec.emplace_back(*it - '0', leftRegNumIdx, // error on this line
                               RegNumIndex{invalid_index});
        auto const litIdx{get_last_index(regNumVec)};
        if (leftRegNumIdx != invalid_index) {
            regNumVec[leftRegNumIdx].rightIdx = litIdx;
        }
        leftRegNumIdx = litIdx;
        return litIdx;
    };
}

auto generate_pair(std::string::const_iterator &it,
                   PairIndex parent = PairIndex{invalid_index}) -> PairIndex {
    pairVec.emplace_back();
    auto const pairIdx{get_last_index(pairVec)};
    auto &pair{pairVec.back()};
    pair.parent = parent;

    ++it;  // skip [
    pair.leftIdx = generate_side(it, pairIdx);
    ++it;  // skip comma
    pair.rightIdx = generate_side(it, pairIdx);
    ++it;  // skip ]

    return pairIdx;
}

int main() {
    auto const snailFishNumbers{readinputdata<std::string>(  //
        std::stringstream{testData}                          //
        )};

    auto const outerPairIdxs{[&] {
        auto outerPairIdxs{std::vector<PairIndex>{}};
        outerPairIdxs.reserve(size(snailFishNumbers));
        transform(cbegin(snailFishNumbers), cend(snailFishNumbers),
                  back_inserter(outerPairIdxs), [](auto const &str) {
                      auto it{cbegin(str)};  // need lvalue
                      return generate_pair(it);
                  });
        return outerPairIdxs;
    }()};

    return 0;
}

>Solution :

Your type is an aggregate

struct RegularNumber {
    using IndexType = RegNumIndex;

    int value{};
    RegNumIndex leftIdx{}, rightIdx{};
};

which construct_at tries to initialize with round parenthesis

::new(std::declval<void*>()) T(std::declval<Args>()...).

However, an aggregate needs a {} initializer, until the compiler implements P0960 Allow initializing aggregates from a parenthesized list of values

And Clang just isn’t there yet. Compiler support for C++20

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading