Error on MSVC when trying to declare std::make_unique as friend of my templated class
Asked Answered
C

2

5

Apparently today, MSVC is trying its best to convince me to switch to clang. But I won't give up. Earlier I asked this question wondering how to declare std::make_unique as a friend of my class.

I got a pretty good answer on my simple scenario and indeed when I tried it with clang on wandbox it compiled just fine.

So I go happy back to Visual Studio 2013 to continue coding. A part of my code is this:

// other includes
#include <string>
#include <memory>

template <typename Loader, typename Painter, typename MeshT>
class Model
{
public:
    friend std::unique_ptr<Model> std::make_unique<Model>(
        const std::string&,
        const std::shared_ptr<Loader>&,
        const std::shared_ptr<Painter>&);

    // Named constructor
    static std::unique_ptr<Model> CreateModel(
        const std::string& filepath,
        const std::shared_ptr<Loader>& loader,
        const std::shared_ptr<Painter>& painter)
    {
        // In case of error longer than the Lord of the Rings trilogy, use the
        // line below instead of std::make_unique
        //return std::unique_ptr<Model>(new Model(filepath, loader, painter));
        return std::make_unique<Model>(filepath, loader, painter);
    }

// ...
protected:
    // Constructor
    Model(
        const std::string& filepath,
        const std::shared_ptr<Loader>& loader,
        const std::shared_ptr<Painter>& painter)
        : mFilepath(filepath)
        , mLoader(loader)
        , mPainter(painter)
    {
    }

// ...

};

Okay, to be honest I didn't expect to get it right on the first time but I was confident I could make some sense out of the error message:

1>d:\code\c++\projects\active\elesword\src\Model/Model.hpp(28): error C2063: 'std::make_unique' : not a function
1>          ..\..\src\Main.cpp(151) : see reference to class template instantiation 'Model<AssimpLoader,AssimpPainter,AssimpMesh>' being compiled

Apparently, MSVC doesn't think that the std::make_unique function is..well..a function.

The worst part is that I am tired and I got this feeling that I am missing something very very very (...) obvious. Can anyone help me unstuck?

Also, can anyone try this with Visual Studio 2015? Just out of curiosity..

Note: I know I could (and probably should) just use return std::unique_ptr<Model>(new Model(filepath, loader, painter)); but it just doesn't feel right.

Chemoreceptor answered 24/11, 2015 at 23:25 Comment(4)
Doesn't compile on VS2015 either, and it looks like an MSVC bug to me. Seems it won't deduce template arguments, you need to specify them explicitly. So if I make my own non-variadic make_unique and then change the friend declaration to friend std::unique_ptr<A> my::make_unique<A, T&>(T&); like the following example, it compiles on MSVC - coliru.stacked-crooked.com/a/a036bc575b552665F
You should stop trying to befriend something in the standard library. In that way lies madness.Uropod
Praetorian: hmm I hoped that vs2015 wouldnt have that problem. Well I guess it is better to forget this approach if I want my project to work on all compilers. Nice workaround by the way. I may use it or something like that.Chemoreceptor
A common technique is to use a public constructor that requires an instance of a private tag type to call.Uropod
L
9

Trying to friend std functions puts you in dangerous territory because you're making assumptions about their implementations that aren't guaranteed by the standard. For example, you want std::make_unique to be a friend so that it can access your protected constructor, but what if std::make_unique's implementation delegates this to some other secret function? What you'd need then is to befriend that secret function, but it's secret, so you can't.

Other complications: Some forms of std::make_unique aren't exactly specified by the standard (though I don't think that applies to this exact example). Old versions of VC++ used macro magic to simulate variadic templates before the compiler had full support for variadic templates, so while there is a std::make_unqiue, it might not have the actual signature you expect.

Lacerta answered 25/11, 2015 at 18:17 Comment(1)
Yeah that makes absolute sense. You are right. Thanks for your answer!Chemoreceptor
S
0

A problem is that different compilers may implement std::make_unique differently. E.g. friend unique_ptr<T> make_unique(Args&...); does not work in Visual Studio 17.7.1 nor GCC 13.2. Here are two slightly different friends that work.

#include <iostream>
#include <memory>
class classA {
    public:
    class classB {
        friend classA;
        #ifdef _MSC_VER
        // Works in Visual Studio 17.7.1     /std:c++20
        template<class _Ty, class... _Types, std::enable_if_t<!std::is_array_v<_Ty>, int> >
        friend std::unique_ptr<_Ty> std::make_unique(_Types&&...);
        #else
        // Works in GCC 13.2.    -std=c++20
        template<typename _Tp, typename... _Args>
        friend std::__detail::__unique_ptr_t<_Tp> std::make_unique(_Args&&...);
        #endif
    public:
        classB() {}
    private:
        classB(const std::string& str) : mVal(str) {}
        std::string mVal;
    };
    std::unique_ptr<classB> make_classB() {
        return std::make_unique<classB>("Hello");
    }
};
int main() {
    classA a;
    auto b = a.make_classB();
}

Regards

Settera answered 24/8, 2023 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.