Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is VS picking the wrong specialisation? Why?

Here's some code:

#include <iostream>
#include <functional>
#include <string>
#include <variant>

namespace detail
{
    std::string Foo(const double val)
    {
        return std::to_string(val);
    }
}

using Value = std::variant<std::monostate, double, int, bool, std::string>;
struct ReadStore {};

struct ValueException : std::runtime_error {};

using PrintConvType = std::function<Value(const Value&, ReadStore&)>;


template <typename InType, typename OutType, typename CallableType, CallableType Callable, bool Strict>
Value ConvWrapperImpl(const Value& value)
{
    if constexpr (std::is_same_v<InType, Value>)
    {
        return Callable(value);
    }
    else
    {
        if (value.index() != 0)
        {
            const auto* ptr = std::get_if<InType>(&value);
            if (ptr)
                return Callable(*ptr);
        }

        return value;
    }
}


template <typename T1, typename T2, T2 (*Callable)(const T1&), bool Strict = false>
Value ConvWrapper(const Value& value, ReadStore&)
{
    return ConvWrapperImpl<T1, T2, decltype(Callable), Callable, Strict>(value);
}

template <typename T1, typename T2, T2 (*Callable)(T1), bool Strict = false>
Value ConvWrapper(const Value& value, ReadStore&)
{
    return ConvWrapperImpl<T1, T2, decltype(Callable), Callable, Strict>(value);
}

int main()
{
    using namespace detail;

    ReadStore store;
    PrintConvType func = ConvWrapper<double, std::string, Foo>;

    Value result = func(3.14159, store);
     std::cout << std::get<std::string>(result) << '\n';
}

It's a contrived MCVE, but the general idea in the original project is to provide a shorthand for wrapping the conversion of Value to the appropriate argument type for some callback function, and for wrapping the conversion of said function's return type back to Value again.

This is fine under GCC and Clang, but my Visual Studio 2017 (v15.7.2) errors out:

error C2440: 'specialization': cannot convert from 'std::string (__cdecl *)(const double)' to 'std::string (__cdecl *)(const double &)'
note: This conversion requires a reinterpret_cast, a C-style cast or a function-style cast
error C2973: 'convWrapper': invalid template argument 'std::string (__cdecl *)(const double)'
note: see declaration of 'ConvWrapper'

Based on where that last note links to, it seems like it's selected the wrong ConvWrapper then acting surprised when the Callable signature doesn't match.

What's weird is that on Godbolt with the same compiler selected, the code is accepted.

Is there a configuration that could be affecting this? Is it me?

How can I fix it? Either with a configuration change or a code change?

like image 554
Lightness Races in Orbit Avatar asked Dec 06 '18 13:12

Lightness Races in Orbit


2 Answers

The compiler flag that causes the issue is the /permissive- conformance flag. Removing it from your compiler command or changing it in Debug -> "name of project" properties -> C/C++ -> Language -> Conformance Mode to No will get it to compile.

like image 84
NathanOliver Avatar answered Oct 06 '22 02:10

NathanOliver


You might avoid the overloads, with c++17 by replacing typename CallableType, CallableType Callable by auto Callable. Then MSVC doesn't need to convert between type.

template <typename InType, auto Callable, bool Strict>
Value ConvWrapperImpl(const Value& value)
{
    if constexpr (std::is_same_v<InType, Value>)
    {
        return Callable(value);
    }
    else
    {
        if (value.index() != 0)
        {
            const auto* ptr = std::get_if<InType>(&value);
            if (ptr)
                return Callable(*ptr);
        }

        return value;
    }
}

template <typename T1, auto Callable, bool Strict = false>
Value ConvWrapper(const Value& value, ReadStore&)
{
    return ConvWrapperImpl<T1, Callable, Strict>(value);
}

with similar usage (ConvWrapper<double, &Foo> instead of ConvWrapper<double, std::string, &Foo>):

int main()
{
    using namespace detail;

    ReadStore store;
    PrintConvType func = ConvWrapper<double, &Foo>;

    Value result = func(3.14159, store);
     std::cout << std::get<std::string>(result) << '\n';
}

Demo

like image 35
Jarod42 Avatar answered Oct 06 '22 01:10

Jarod42