Частичное упорядочивание с шаблоном функции, не выводившим контекст

String.prototype.format = function(){
    var final = String(this);
    for(let i=0; i<arguments.length;i++){
        final = final.replace(`%s${i+1}`, arguments[i])
    }
    return final || ''
}

console.log(("hello %s2 how %s3 you %s1").format('hi', 'hello', 'how'));
<h1 id="text">
   
</h1>
22
задан Deduplicator 21 October 2018 в 23:55
поделиться

4 ответа

Вот моя попытка. Я согласен с Чарльзом Бейли в том, что неправильный шаг - перейти от Const :: Type * к void *

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

Шаги, которые мы хотим предпринять:

14.5.5.2/2

Учитывая два перегруженных шаблона функций, можно определить, является ли один более специализированным, чем другой, путем преобразования каждого шаблона по очереди и использования вывода аргументов (14.8.2) для сравнения его с другим.

14.5.5.2/3-b1

Для каждого параметра шаблона типа синтезируйте уникальный тип и замените его для каждого вхождения этого параметра в списке параметров функции или для функции преобразования шаблона в типе возвращаемого значения.

На мой взгляд, типы синтезируются следующим образом:

(Q, Const<Q>::Type*)    // Q1
(Q, void*)              // Q2

Я не Я не вижу формулировку, которая требует, чтобы второй синтезируемый параметр T1 был void * . Я также не знаю прецедента в других контекстах. Тип Const :: Type * является совершенно допустимым типом в системе типов C ++.

Итак, теперь мы выполняем шаги вывода:

Q2 to T1

Мы пытаемся вывести параметры шаблона для T1, поэтому у нас есть:

  • Параметр 1: T вычисляется как Q
  • Параметр 2: Невыведенный контекст

Даже если параметр 2 является невыведенным контекстом , дедукция по-прежнему успешна, потому что у нас есть значение для T.

Q1 до T2

Вычитая параметры шаблона для T2, мы получаем:

  • Параметр 1: T вычисляется как ] Q
  • Параметр 2: void * не соответствует Const :: Введите * , значит, вычет сбой.

ИМХО, вот где стандарт нас подводит. Параметр не зависит, поэтому не совсем понятно, что должно произойти, однако мой опыт (на основе прищуренного чтения 14.8.2.1/3) показывает, что даже если тип параметра P не зависит, тогда тип аргумента A должен соответствовать это.

Синтезированные аргументы T1 могут использоваться для специализации T2, но не наоборот. Таким образом, T2 более специализирован, чем T1, и поэтому является лучшей функцией.


ОБНОВЛЕНИЕ 1:

Просто чтобы скрыть предположение о том, что Const :: type недействителен. Рассмотрим следующий пример:

template<typename T>
struct Const;

template<typename T>
void f(T, typename Const<T>::type*) // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T>
void f(T, void*)                    // T2
{ typedef typename T::TYPE2 TYPE ; }

template<>
struct Const <int>
{
  typedef void type;
};

template<>
struct Const <long>
{
  typedef long type;
};

void bar ()
{
  void * p = 0;
  f (0, p);
}

В приведенном выше примере Const :: type используется, когда мы выполняем обычные правила разрешения перегрузки, но не когда мы переходим к правилам частичной перегрузки. Было бы неправильно выбирать произвольную специализацию для Const :: type . Это может быть не интуитивно понятно, но компилятор вполне счастлив иметь синтезированный тип формы Const :: type * и использовать его при выводе типа.


ОБНОВЛЕНИЕ 2

template <typename T, int I>
class Const
{
public:
  typedef typename Const<T, I-1>::type type;
};

template <typename T>
class Const <T, 0>
{
public:
  typedef void type;
};

template<typename T, int I>
void f(T (&)[I], typename Const<T, I>::type*)     // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T, int I>
void f(T (&)[I], void*)                           // T2
{ typedef typename T::TYPE2 TYPE ; }


void bar ()
{
  int array[10];
  void * p = 0;
  f (array, p);
}

Когда шаблон Const создается с некоторым значением I , он рекурсивно создается до тех пор, пока I не достигнет нуля. Это происходит, когда частичная специализация Const выбрано. Если у нас есть компилятор, который синтезирует какой-то реальный тип для параметров функции, то какое значение компилятор выберет для индекса массива? Скажи 10? Что ж, это было бы хорошо для приведенного выше примера, но не соответствовало бы частичной специализации Const , что, по крайней мере, концептуально, привело бы к бесконечному количеству рекурсивных экземпляров первичного. Какое бы значение он ни выбрал, мы могли бы изменить конечное условие на это значение + 1, и тогда у нас был бы бесконечный цикл в алгоритме частичного упорядочения.

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

6
ответ дан 29 November 2019 в 05:55
поделиться

Преобразованный список параметров для T1 (Q вставлен): (Q, typename Константа :: тип *). Типы аргументы AT = (Q, void *)

Интересно, действительно ли это правильное упрощение. Когда вы синтезируете тип Q , разрешено ли вам вызывать специализацию для Const с целью определения порядка спецификации шаблонов?

template <>
struct Const<Q> { typedef int type; }

Это будет означать, что T2 по крайней мере не так специализирован, как T1 , потому что параметр void * не соответствует второму параметру T1 ни для одного заданного параметра шаблона.

1
ответ дан 29 November 2019 в 05:55
поделиться

Изменить: после изучения реализации Clang (Дугом Грегором) их алгоритма частичного упорядочивания, я пришел к согласию с остальными плакатами, что исходный пример не является «задумано» быть двусмысленным - даже несмотря на то, что стандарт не так ясен, как мог бы, о том, что должно происходить в таких ситуациях. Я отредактировал этот пост, чтобы указать свои исправленные мысли (для моей же пользы и справки). В частности, алгоритм Кланга пояснил, что « typename Const :: type » не переводится в «void» на этапе частичного упорядочивания - и что каждая пара A / P выводится независимо друг от друга.

Первоначально я задавался вопросом, почему следующее считалось неоднозначным:

        template<class T> void f(T,T*);  // 1

        template<class T> void f(T, int*); // 2

        f(0, (int*)0); // ambiguous

(Вышеупомянутое неоднозначно, потому что нельзя вывести f1 (U1, U1 *) из f2 (T, int *), и, идя другим путем, нельзя вывести f2 (U2, int *) из f1 (T, T *). Ни один из них не является более специализированным.)

но следующее не будет двусмысленным:

        template<class T> struct X { typedef int type; };
        template<class T> void f(T, typename X<T>::type*); // 3
        template<class T> void f(T, int*); // 2

(Причина, по которой можно было бы ожидать, что это будет двусмысленным, состоит в том, если бы произошло следующее:
- f3 (U1, X :: type *) -> f3 (U1, int *) ==> f2 (T, int *) (вывод ОК, T = U1)
- f2 (U2, int *) ==> f3 (T, X :: type *) (вывод ok, T = U2 делает X :: type * -> int *)
Если это были правдой, ни один из них не был бы более специализированным, чем другой.)

После изучения алгоритма частичного упорядочивания Кланга становится ясно, что они рассматривают «3» выше, как если бы это было так:

template<class T, class S> void f(T, S*); // 4

поэтому выведение некоторого уникального «U» против 'typename X :: type' будет успешным -

  • f3 (U1, X :: type *) обрабатывается как f3 (U1, U2 *) ==> f2 (T, int *) (вывод не подходит )
  • f2 (U2, int *) ==> f3 (T, S * [[X :: type *]]) (вывод нормально, T = U2, S = int)

И так «2» явно более специализировано, чем «3».

2
ответ дан 29 November 2019 в 05:55
поделиться

Изменить: пожалуйста, не обращайте внимания на этот пост - После изучения алгоритма clangs для частичного упорядочивания, реализованного Дугом Грегором (хотя он реализован только частично на момент написания этой статьи - кажется, что логика, имеющая отношение вопрос OP реализован достаточно адекватно) - похоже, что он рассматривает недооцененный контекст как просто еще один параметр шаблона. Это предполагает, что перегрузка с явным аргументом void * должна быть более специализированной версией и не должно быть двусмысленности. Как обычно, Комо прав. Теперь что касается формулировки в стандарте, которая четко определяет это поведение - это другое дело ...

Поскольку этот пост был также размещен на comp.lang.c ++. Moderated, и, похоже, там тоже есть некоторая путаница - я подумал Я бы отправил свой ответ этой группе и здесь - поскольку обсуждение, очевидно, имеет отношение к заданному здесь вопросу.

25 июля, 13:11, Барт ван Инген Шенау < b ... @ ingen.ddns. Информация> wrote:

You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const such that Const::type != void?

As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, *). To call B with these parameters requires an implicit conversion (* to void*) and therefore A is less specialised than B.

I believe this is incorrect. When checking to see which function is more specialized (during partial-ordering), the compiler transforms the parameter-list to (Q, void*) - i.e. it actually instantiates the relevant template (best matching) and looks inside it for the value of 'type' - in this case, based on the primary template, it will be void*.

Regarding your point concerning partial specialization - when checking for which template is more specialized than the other, the only type that can be used is the unique generated type - if there are other specializations at the point of instantiation of the declaration (when overload resolution is being done) they will be considered. If you add them later, and they should get selected you will be violating the ODR (according to 14.7.4.1)

The partial/explicit specializations will also get considertaion during formation of the candidate set - but this time using the types of the actual arguments to the function. If the best matching partial specialization (of X) results in a function type that has a better implicit conversion sequence for some параметр, то мы никогда не дойдем до фазы частичного упорядочения, и что "Лучшая" функция будет выбрана (перед тем, как перейти к частичной этап упорядочивания)

Вот пример с комментариями о том, что должно происходить на различных этапах:

    template<class T, bool=true> struct X;  // Primary

    template<class T> struct X<T,true> { typedef T type; };  // A
    template<> struct X<int*,true> { typedef void* type; };  // B


    template<class T> void f(T,typename X<T>::type); //1
    template<class T> void f(T*,void*); //2


    int main()
    {
      void* pv;
      int* pi;


      f(pi,pi);   
      // two candidate functions: f1<int*>(int*,void*),  f2<int>(int*,void*)
      // Note: specialization 'B' used to arrive at void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity 




      f(pv,pv);  
      // two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
      // Note: specialization 'A' used to arrive at second void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity again             

    }

Также стоит упомянуть, что если первичный шаблон не имеет определения - тогда SFINAE работает на этапе частичного упорядочивания, ни одна из них не может быть выведена из другой, и в результате должна возникнуть двусмысленность.

Также, если вы добавите еще один шаблон, который приведет к другому совпадению, если точка создания любой из этих функций будет перемещена в другое место в единице перевода, вы явно нарушите ODR.

25 июля, 13:11, Барт ван Инген Шенау < b ... @ ingen.ddns.info > написал:

Во-первых, более специализированный подход означает, что их меньше типы, где этот шаблон можно выбрать по разрешению перегрузки. Используя это, правила частичного упорядочивания можно резюмировать следующим образом: Попробуйте найти такой тип для A, чтобы A можно было вызвать, но B нет, или перегрузить разрешение предпочитает называть A. Если этот тип может быть найден, то B больше специализированный, чем A.

Здесь нет аргументов. Но исходя из текущих правил, пример OP должен быть неоднозначно.


Наконец, вот явные, однозначные ответы на два конкретных вопроса, поднятых litb:

1) Будет ли теперь использоваться значение T, выведенное для первого параметра?
Да - конечно, это должен, он выполняет вывод аргументов шаблона - «ссылки» должны поддерживаться.

2) Теперь, почему реализации говорят, что второй является более специализированным?
Потому что они ошибаются;)

Я надеюсь, что это решит проблему - Пожалуйста, дайте мне знать, если что-то еще неясно :)

Изменить: litb поднял хороший момент в своем комментарии - возможно, заявив, что первичный шаблон всегда будет используется для создания экземпляра с уникальным сгенерированным типом - слишком сильное утверждение.
В некоторых случаях основной шаблон не вызывается.
Я понимаю, что при частичном упорядочивании некий уникальный сгенерированный тип используется для соответствия лучшей специализации. Вы правы, это не обязательно должен быть основной шаблон. Для этого я отредактировал вышеуказанный язык. Он также поднял вопрос об определении лучшего совпадающего шаблона после момента создания. That will be a violation of the ODR according to the section on point of instantiation.


The standard says that once the A/P pairs are created (using the rules of transformation as described in temp.func.order) they are deduced against each other using template argument deduction (temp.deduct)- and that section handles the case of non-deduced contexts, instantiating the template and its nested type, triggering points of instantiations. The temp.point section handles the ODR violations (the meaning of partial ordering should not change regardless of the points of instantation within a translation unit). I'm still not sure where the confusion is coming from? – Faisal Vali 1 hour ago [delete this comment]

litb: "Note that the step that puts Q into Const::type to build the arguments is not covered explicitly by the SFINAE rule. Правила SFINAE работают с выводом аргументов, поместите абзацы, которые помещают Q в список параметров функции шаблона функции, в 14.5.5.2. '

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

Позвольте мне предоставить один способ связать их. Из (14.8.2): "Если указан явный список аргументов шаблона, аргументы шаблона должны быть совместимы с список параметров шаблона и должен приводить к допустимому типу функции, как описано ниже; в противном случае вычет типа не работает "

Из (14.5.5.2/3) "Используемое преобразование: - Для каждого параметра шаблона типа синтезируйте уникальный тип и замените его для каждого вхождения этот параметр в списке параметров функции или для функции преобразования шаблона в возвращаемом типе. "

На мой взгляд, приведенная выше цитата подразумевает, что как только вы" создаете "уникальные сгенерированные типы для каждого параметра шаблона, в объявлении функции быть имплицитность, созданная явным образом , предоставляющая уникальные типы в качестве аргументов шаблона нашей функции-шаблону. Если это приводит к недействительному тип функции, то не только преобразование, но, что более важно, последующий вывод аргументов шаблона, необходимый для частично упорядочить функцию не удается.

Из (14.5.5.2/4) "Используя преобразованный список параметров функции, выполните выведение аргументов для другого шаблона функции. Преобразованный шаблон по крайней мере так же специализирован, как и другие , если и только если вывод успешен и выведенные типы параметров являются точным совпадением (так что вывод не полагается на неявные преобразования). "

Если преобразованный список параметров функции приводит к неудачной подстановке, то мы знаем, что дедукция не могла быть успешной. А поскольку дедукция не увенчалась успехом, она не так специализирована, как другая - это все, что нам нужно знать, чтобы продолжить. при частичном упорядочивании двух.

litb: Я также не уверен, что происходит в этом случае: template struct A; template void f (T, typename A < T> :: type); template void f (T *, typename A :: type); конечно, с отступом, чтобы быть допустимым кодом, но при выполнении A :: type он завершится неудачно, потому что в контекст определения шаблона, A еще не определен " Также обратите внимание, что для создания экземпляров шаблонов в результате этого не определено POI. вид замены при попытке определить порядок (частичное упорядочение не зависит в любом контексте. Это статическое свойство двух задействованных шаблонов функций). Я думаю, это похоже на проблему в Стандарте, которую необходимо исправить.

Хорошо - я думаю, я вижу, где мы видим вещи по-другому. Если я правильно вас понял, вы говорите, что когда эти шаблоны функций объявляются, компилятор отслеживает частичный порядок между ними, независимо от разрешения перегрузки, когда-либо запускаемого для выбора между ними. Если вы так интерпретируете это, то я понимаю, почему вы ожидаете описанного выше поведения. Но я не думаю, что стандарт когда-либо требует или предписывает это.

Теперь стандарт ясно, что частичное упорядочение не зависит от типа, который используется при вызове функции (я считаю это то, что вы имеете в виду, когда описываете его как статическое свойство, независимое от контекста).

Стандарт также ясно, что он заботится только о частичном упорядочивании (вызывает частичное упорядочение) между шаблонами функций в процессе разрешения перегрузки (13.3.3 / 1), если и только если он не может выбрать лучшую функцию на основе ICS или если один является шаблоном, а другой - нет. [Частичное упорядочение частичных специализаций шаблона класса - отдельная тема и, на мой взгляд, использует соответствующий контекст (другие определения шаблонов), который требует создания экземпляра этого конкретного класса.]

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

Итак, исходя из моего предположения, согласно вашему примеру с использованием «структуры шаблона A» выше, код действителен. Частичное упорядочивание не выполняется в контексте определения. Но если / когда вы вызываете разрешение перегрузки между двумя функциями, записав вызов f ((int *) 0,0) - и в то время, когда компилятор либо пытается собрать объявление кандидата или частично упорядочить их (если доходит до этапа частичного упорядочивания) если недопустимое выражение или тип является частью типа функции, SFINAE помогает нам и сообщает нам, что вывод шаблона не работает (что касается частичного упорядочивания, это означает, что не может быть более специализированным, чем другой, если бы мы не могли даже преобразовать шаблон).

Теперь что касается POI - если вы, как и я, убеждены, что преобразованные типы функций должны представляют неявные экземпляры с использованием явно предоставленных списков аргументов шаблона (с использованием уникально сгенерированных типов) тогда уместны следующие стандартные кавычки:

14.6.4.1/1 Для специализации шаблона функции, специализации шаблона функции-члена или специализации для функция-член или статический член данных шаблона класса, если специализация создается неявно потому что на него ссылаются из другой специализации шаблона и из контекста, из которого на него ссылаются зависит от параметра шаблона, точка инстанцирования специализации - это точка инстанцирования охватывающей специализации.

Я интерпретирую это так: POI преобразованного типа функции и исходного типа функции является то же самое, что и POI для тех функций, которые создаются фактическим вызовом функции.

litb: Поскольку частичное упорядочивание - это скорее всего свойство синтаксической формы параметров (например, «T *» вместо «T (*) [N]»), я бы проголосовал за внесение поправок в спецификацию (например, «если Q появляется во вложенном описателе имени a qualified-id naming a type, then the type named is "Q") Or saying that the type named is another unique type. This means that in template void f(T, typename Const::type*); the argument list is (Q, R*), for example. Same for template void f(T*, typename ConstI::type); the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course. I would have to think about it and make some test cases to see if this would yield natural orderings, though.

Aah - now you are suggesting a possible solution that resolves the ambiguity in favor of what we all intuitively expect - this is a separate problem, and while I like the direction you are heading in, как и вы, мне тоже нужно было бы немного подумать, прежде чем объявить о его работоспособности.

Спасибо за продолжение обсуждения. Я бы хотел, чтобы вы не ограничивали вас только размещением комментариев.

Поскольку вы можете редактировать мои сообщения, пожалуйста, не стесняйтесь отвечать в сообщении, если это будет проще.

1
ответ дан 29 November 2019 в 05:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: