在源文件上定义 `static constexpr` 函数

Define `static constexpr` function on source file

我在 header 文件上有一个 class,它的成员在 pimpl class 中定义。这个想法是我使用 this 方法(基本上是 std::aligned_storage_t 和一个指针,但是在声明 object 时必须指定 class 的大小和对齐方式)来分配堆栈上的 pimpl class。我想使代码 cross-compiler 所以猜测不是一个选项,因此我定义了 2 private static constexpr 函数:impl_sizeimpl_align 定义在相应的源文件,基本上 return sizeof(pimpl)alignof(pimpl)。问题是我从 MSVC 收到以下错误(未在其他编译器上测试):

expression must have a constant value -- constexpr function function "Type::impl_size" (declared at line 70) is not defined

第 70 行是 impl_size 在 header 上定义的地方。

MCVE:

#include <cstddef>

template <typename T1, std::size_t N1, std::size_t N2>
struct Pimpl;

class Type
{
  private:
    static constexpr std::size_t impl_size() noexcept; // line 70
    static constexpr std::size_t impl_align() noexcept;

    struct impl {};

    Pimpl<impl, impl_size(), impl_align()> data; // error happens here
};

constexpr std::size_t Type::impl_size() noexcept
{
    return sizeof(Type::impl);
}

constexpr std::size_t Type::impl_align() noexcept
{
    return alignof(Type::impl);
}

这就是它所说的:你在声明它时没有定义你的成员函数constexpr

您必须立即提供定义,内嵌在 class 定义中。

您的代码有几个问题让我觉得很奇怪。 “PImpl”习语的要点是将接口与其实现分离,通常是为了在实现更改时允许更容易的编译。但是,通过在接口 class 中定义 struct impl 并在同一 class 的模板中使用它,您实际上是在强制实现与接口相结合。

sizeofalignof 需要一个完整的类型,其中 impl 似乎不是 (EDIT 在撰写本文时,impl 只是前向声明),因此即使在解决了 Type::impl_size()Type::impl_align() 的问题后,您也会遇到这个问题。

对您的问题的一个直接但有点肤浅的解决方法是使 impl 成为一个完整的类型(并在声明时定义它),并使 impl_size()impl_align()inline static constexpr 函数中,这些函数也是当场定义的:

class type
{
  // ...

  private:
    struct impl {
        // struct definition, so that impl is a complete type
    };

    inline static constexpr impl_size() noexcept {
        return sizeof(impl);
    }
    inline static constexpr impl_align() noexcept {
        return alignof(impl);
    }
    
    Pimpl<impl, impl_size(), impl_align()> data;
};

这仍然有点味道,因为 impl_sizeimpl_align 是毫无意义的样板,而您的 PImpl 模板可以直接从其第一个参数获得所有相同的信息:

template<typename T>
class Pimpl {
    static constexpr std::size_t Size = sizeof(T);
    static constexpr std::size_t Alignment = alignof(T);
};

class type {
  private:
    struct impl {};
    
    Pimpl<impl> data;
};

这当然也要求impl是一个完整的类型。还有 this will be required anyway if struct impl is a nested cless.

看来您正在尝试在此处进行某种类型的擦除(或者是否有其他充分的理由需要 impl 的大小和对齐方式?),但无意中引入了很多依赖项关于实现和涉及的类型。

我建议如下:在命名空间范围内转发声明您的 impl class,并简单地使用 std::unique_ptr<impl> 来实现。然后 impl 可以在你的实现文件中定义。请注意 std::unique_ptr 不需要完整类型。

#include <iostream>
#include <memory>

// header.hpp
struct impl;

class type {
public:
    type();
    void doThing();
    
private:
    std::unique_ptr<impl> m_pImpl;
};

// source.cpp
struct impl {
    void doThingImpl() {
        std::cout << "Did a thing";
    }
};

type::type()
    : m_pImpl(std::make_unique<impl>()) {
      
}

void type::doThing() {
    m_pImpl->doThingImpl();
}

int main(){
    auto t = type{};
    t.doThing();
    
    return 0;
}

Live Demo